Protect your API Resource with RBAC
This page assumes you have configured roles and permissions based on your demand. Go through ๐ RBAC (Role Based Access Control) first if you haven't done yet.
Serverโ
To ensure the incoming request has the right access, you need to validate Access Token on the server side. Detailed validation process is demonstrated in โ๏ธ Protect your API.
In short, besides the basic JWT and JWS validtion, you need to focus on:
- The token has the correct issuer (
iss
) - The token has the correct audience (i.e. resource indicator,
aud
) - The token has the desired scope (i.e. permission,
scope
)
Note scope
is a string that includes all granted scopes separated by space. Usually the library you use to validate will take care of it.
Currently, there's no limitation for requesting valid resource indicators in an auth flow. Thus validating scope
(permission) is crucial when implementing RBAC. Otherwise the meaning of resource indicator will become trivial.
You may notice that role
is missing in the Access Token, which is by design since the scope (permission) of an API Resource is the final representative of the access control model and what we only care about.
Clientโ
Assumptionโ
Let's say you have two resources with permissions as below:
- API Resource
https://api-1.store.io
- Permission
read:order
- Permission
write:order
- Permission
- API Resource
https://api-2.store.io
- Permission
read:order
- Permission
To gain the specific access, the client needs to exchange a proper Access Token from Logto, and use it as a Bearer Token in the request header.
Configure client SDKโ
This tutorial uses TypeScript as the sample language, and the convention applies to all Logto Client SDKs.
When integrating client SDK with your application, make sure to include both resources
and scopes
(permissions) in the configuration for LogtoClient
.
For example, in order to access the API Resource https://api-1.store.io
and fetch read:order
and write:order
permissions, the config should look like:
const config: LogtoConfig = {
// ...other configs
resources: ['https://api-1.store.io'],
scopes: ['read:order', 'write:order'], // i.e. permissions
};
In Logto SDKs, the term scope
is used to align with the OAuth 2.0 protocol. However, in the Admin Console, it is referred to permission
for better readability and understanding of real-world scenarios, in line with the NIST RBAC model. Thus scope
and permission
are identical and exchangeable in all cases except coding.
Fetch an access token for a resourceโ
When user has successfully signed in, you can use logtoClient.getAccessToken()
to fetch an access token for a given resource:
- TypeScript
- Swift
- Kotlin
const accessToken = await logtoClient.getAccessToken('https://api-1.store.io');
let token = try await client.getAccessToken(for: "https://api-1.store.io")
logtoClient.getAccessToken("https://api-1.store.io", { logtoException, accessToken ->
//...
})
The Access Token will include all eligble scopes (permissions) for the current user. The definition of eligble scopes is the joint of the following sets:
- A subset of
scopes
in the initial Logto config that includes all the scopes that belong to the resource indicator you passed ingetAccessToken()
- The scopes that the user can obtain based on your RBAC configuration
For example, in the initial Logto config:
const config: LogtoConfig = {
// ...other configs
resources: ['https://api-1.store.io'],
scopes: ['read:order', 'write:order', 'custom_data'],
};
Per our assumption, scope custom_data
is not available in the API Resource https://api-1.store.io
. When getting an Access Token:
const accessToken = await logtoClient.getAccessToken('https://api-1.store.io');
If the user has both read:order
and write:order
permissions for the API Resource https://api-1.store.io
, the returned Access Token will have both scopes:
{
// ...other token claims
iss: '<your-logto-endpoint>/oidc', // issuer
aud: 'https://api-1.store.io', // audience, i.e. resource indicator
scope: 'read:order write:order'
}
If no resource indicator is passed in getAccessToken()
, it will try to fetch an Access Token for the UserInfo Endpoint. Check out the "Fetch user information" section in the SDK integration guide for details.
While you can specify multiple resource indicators in the config, getAccessToken()
only accepts a single resource parameter for security reason. As a result, Access Tokens are resource-specific.
Carry the Access Token in requestsโ
Put the token in the Authorization
field of HTTP headers with the Bearer format (Bearer YOUR_TOKEN
), and you are good to go. Now https://api-1.store.io
can receive and validate the token from the server side.
The Bearer Token's integration flow may vary based on the framework or requester you are using. Choose your own way to apply the request Authorization
header.
Optional: Access multiple API Resourcesโ
While your business or the amount of micro services is growing, your client app may need to access multiple API Resources. We found it confusing since we cannot specify the relation between resources
and scopes
per OAuth 2.0 protocol.
So we took another look at RFC 8707. In section 2.2, it turns out the result is the cartesian product of resources
and scopes
:
... Effectively, the requested access rights of the token are the cartesian product of all the scopes at all the target services. To the extent possible, when issuing access tokens, the authorization server should downscope the scope value...
OK cool. Let's go with this.
Thus, if the client wants to access all read:order
permissions across the two resources in our assumption, the config should look like:
const config: LogtoConfig = {
// ...other configs
resources: ['https://api-1.store.io', 'https://api-2.store.io'],
scopes: ['read:order'],
};
During the auth flow, Logto will try to validate two permissions:
- Permission
read:order
of the API Resourcehttps://api-1.store.io
- Permission
read:order
of the API Resourcehttps://api-2.store.io
What if you want to request the write:order
permission of API Resource https://api-1.store.io
? Simply update the config to:
const config: LogtoConfig = {
// ...other configs
resources: ['https://api-1.store.io', 'https://api-2.store.io'],
scopes: ['read:order', 'write:order'],
};
Per definition of the cartesian product, during the auth flow, Logto will try to validate four permissions:
- Permission
read:order
of the API Resourcehttps://api-1.store.io
- Permission
write:order
of the API Resourcehttps://api-1.store.io
- Permission
read:order
of the API Resourcehttps://api-2.store.io
- Permission
write:order
of the API Resourcehttps://api-2.store.io
But the write:order
permission does not exist in the API Resource https://api-2.store.io
. Logto will ignore (i.e. "downscope") this permission in the API Resource https://api-2.store.io
which will reflect in the auth result.
Now you can also specify https://api-2.store.io
as the resource indicator when calling logtoClient.getAccessToken()
. Only read:order
will present in the scope
claim of the returned Access Token even if the user has the full access of https://api-2.store.io
.