Claims Best Practices
On this page
OAuth 2.0 Scopes and Claims
The Scope Best Practices article provides architectural advice to enable you to design scopes at scale. The result is that clients cannot use access tokens at API endpoints outside of the client's remit. For each scope granted, the authorization server issues a set of claims to the access token. Claims are the main ingredient you use to authorize API requests. The Introduction to Claims article provides a summary that explains why you should deliver security-related values to APIs in cryptographically verifiable access tokens.
Claims are trusted assertions
Claims are assertions that allow APIs to trust attributes about a user.
Claims provide secure values to APIs so that they can correctly restrict access to data. Your authorization also includes domain-specific data that may or may not be included in access tokens. You should design scopes and claims together. This article explains how to design claims at scale, avoid common problems and use some advanced claims techniques.
Example Business Scenario: APIs and Clients
The Scope Best Practices article uses the following example, with components for a system that sells products to online customers. Commonly, a system includes multiple APIs and clients that span a number of business areas. You need to ensure that all components can send or receive claims in access tokens.
The article identified the example authorization rules in the following table, based on resource ownership. These rules are dynamic in nature and depend on the authenticated user which your APIs must be identify. This is where claims come into play.
User Role | Authorization Rule |
---|---|
Customer | A customer can only view benefits and orders associated to their own user ID. |
Customer | A customer with a higher subscription level can access additional inventory. |
Administrator | An administrator may have access to all data, but with regional restrictions. |
Supplier User | A supplier business partner can only view inventory for their own company's supplier ID. |
Claims Concepts
Claims provide an extensible mechanism for API authorization that you can scale to large API platforms. Claims are attributes that can originate from multiple sources.
User Attributes
APIs would need to receive user attributes, such as those listed below, to implement the example authorization rules. The authorization server calculates the runtime values of these attributes after user authentication and consent.
- User ID
- User Role
- Customer Subscription Level
- Supplier Partner ID
To enable the issuance of a claim to an access token you configure your authorization server to include the claim in one or more scopes. When a client requests a scope that contains the claim, the authorization server issues the claim value to the access token.
Secure Communication
Sending user attributes in access tokens is a secure design. Avoid sending security-related identifiers in URLs or HTTP headers. For example, the following URL includes a customer ID. A malicious party that intercepts the request could potentially replace the customer ID with a different one, to cause a data breach. Therefore, issue the customer ID to an access token instead.
- /customers/23/orders
Claims vs. Roles
Claims represent a superset of roles, that is roles are a claim. An authorization rule that only considers roles implements role-based access control (RBAC). An API enforcing this article's example authorization rules would use a roles claim in conjunction with other claims. When you consider multiple claims, you get attribute-based access control (ABAC).
User Account Claims
You can store some user attributes in centralized user account records that your authorization server manages. Typically you include informational values like the username and email. You might store other attributes like the country code or social security number. You should be able to store any custom user attributes in your authorization server. After users authenticate, you can look up user account claims and issue them to access tokens.
Claims from Data Sources
Your authorization server should be able to look up claims values from multiple data sources, such as a SQL or NoSQL database, an LDAP store, or an API endpoint that you provide. Once the authorization server resolves claims values it issues them to access tokens. You can store user attributes wherever you prefer and there should be no limitations on the claims that you can issue.
Claims Design
When you design claims, first ensure that APIs have the values they need to authorize correctly. Next, consider how you will scale the architecture and use claims in a maintainable way. Ensure that you could scale your claims design to many API development teams.
Capture Business Identifiers
Claims for business authorization often originate from identifiers within your business data. Order-related data for the example business scenario would most commonly be stored in several separate tables or databases. Some kind of foreign key would link owners or creators to secured resources:
In this partial data schema the User ID
is a familiar business identifier for the user. Inventory is provided via supplier companies, each of which has a Partner ID
.
Design Claims Resolution
For each user account the authorization server issues a subject
claim to identify the user. This is often a generated value like a UUID. To identify the user in business terms to your APIs, you typically need to map the subject claim to your existing user ID(s). One option is to import a user ID into the authorization server's user account data, so that the authorization server can easily issue it to access tokens.
The authorization server can send values like the subject claim to external data sources to resolve custom claims values. For example, the authorization server might call an API endpoint that you provide to resolve custom attributes stored in your business data. The authorization server can then issue the custom attributes as access token claims.
You configure your authorization server to do any work required to resolve claims after which you can design your API authorization code.
Design a Claims Principal
When your APIs receive an access token, they cryptographically verify it and then trust the token's payload. In your API code, you should use an object to represent this payload. Some technology stacks populate this object for you after your API validates the JWT access token. Your technology stack may also give this object a name like the ClaimsPrincipal
. In the example business scenario, the inventory API might use the following claims principal. Other APIs might use slightly different objects:
export class ClaimsPrincipal {scope: string[];sub: string;userId: number;userRole: string;subscriptionLevel: string;partnerId: number;}
In addition to the primitive values in this example, claims can be arrays or objects. For example, some industries may restrict data access using array claims that represent the user's allowed locations or areas of responsibility.
Use Stable Claims
The claims that you issue to access tokens should remain stable and not change within a user's authentication session. Aim to design immutable payloads so that you never run into issues where your APIs receive stale data in access tokens.
When you decide which values belong in the claims principal, adopt a 90/10 rule and only include frequently used claims. Identity-related values such as user and company IDs are usually always good choices:
- 90% of API calls: A data value used frequently for authorization in most API endpoints.
- 10% of API calls: A data value used rarely, perhaps only at a single API endpoint.
Avoid Claims Explosion
Claims in access tokens do not need to provide every possible value that APIs use for authorization. For example, many systems use volatile fine-grained business permissions that you change or redesign frequently:
- A product assigns many fine-grained permissions to roles, prefixes each rule with
can_
and configures them in an administration application. - You use multiple product lines where roles and permissions have different meanings and you consider each product to be its own bounded context.
In such cases, you only need to issue sufficient claims to ensure that your API can identify users and authorize correctly. Your APIs can look up other claims, like volatile business permissions, from claims in the access token. This approach enables API developers to easily evolve their API's use of permissions.
Configuring excessive claims in the authorization server can have an adverse impact on your productivity. Instead, you should only need to add new claims occasionally.
- Avoid the need for access token versioning in your APIs.
- Avoid requiring frequent claims configuration updates to your authorization server.
Add Claims for High-Privilege Operations
In some cases, an API cannot authorize solely from static values like user IDs and roles. Instead, the API needs to receive dynamic claims that provide runtime context and enable the API to authorize correctly. For example, a level_of_assurance
claim might inform APIs of the authentication strength, and APIs might deny access to high privilege operations unless the required level is present.
Before high-privilege operations, clients can issue an authorization redirect to the authorization server with a scope that requests a high-privilege access token. This scope can include a set of dynamic claims that the authorization server resolves. For example, in a payment transaction, the authorization server might interact with a third-party system to resolve claims and issue them to the access token. The API can then use these claims to authorize and complete the operation.
Implement Claims-Based Authorization
At runtime, your API must first validate a JWT access token on every request. Tutorials are available in API Guides to show how to do this in various technologies. The JWT payload contains data that includes custom claims. You deserialize this into a claims principal object for the current request. You then use the claims principal data in your business logic to apply your authorization rules.
{"sub": "556c8ae33f8c0ffdd94a57b7eab37e9833445143cf6847357c65fcb8570413c4","purpose": "access_token","iss": "https://localhost:8443/oauth/v2/oauth-anonymous","active": true,"token_type": "bearer","client_id": "web-client","aud": "api.company.com","nbf": 1611673855,"scope": "openid profile userid transactions","exp": 1611674155,"delegationId": "93f036b6-7cdc-4f9e-89a6-2ab5ec635fbc","iat": 1611673855,"user_id": 123,"user_role": "customer","user_subscription_level": "gold","user_company_id": 0}
Use Claims to Filter Collections
The following code filters on orders owned using a user ID that the API receives in the access token. Since this is a familiar business identity, it is straightforward for the API to implement its checks.
public getOrderItems(): OrderItem[] {if (!claimsPrincipal.hasScope("order:item"))throw forbiddenError();}if (claimsPrincipal.hasRole(ADMIN_ROLE)) {return repository.getAllOrderItems(criteria);} else if (claimsPrincipal.hasRole(CUSTOMER_ROLE)) {return repository.getFilteredOrderItems(claimsPrincipal.userId);} else {throw notFoundError();}}
Use Claims to Enforce Resource Access
When specific resources are requested, an API must determine whether to allow the requested access. In this example, if a user attempts to access data from another supplier they are denied access:
public getInventorySales(id: number): InventorySales {if (!claimsPrincipal.hasScope("inventory:sales"))throw forbiddenError();}const sales = this.repository.getInventorySales(id);if (claimsPrincipal.hasRole(SUPPLIER_ROLE)) {if (sales.partnerId != claimsPrincipal.companyId) {throw notFoundError();}}if (!claimsPrincipal.hasRole(ADMIN_ROLE) && !claimsPrincipal.hasRole(SUPPLIER_ROLE)) {throw notFoundError();}return sales;}
Handle Unauthorized Responses
The above examples use the standard 403 Forbidden
HTTP status code when the API receives an access token with insufficient scope. When access is denied due to the values of runtime claims, you may prefer to return a 404 Not Found
error, if you want the client to receive the same response for both non-existing and unauthorized resources.
Combine Claims with Volatile Business Permissions
The following code shows an example that uses access token claims and also volatile business permissions to decide whether a user can view an order's delivery status. Since this is a domain-specific concern you do not configure the details in your authorization server. Instead, the claims in the access token include stable identity values for the user's role
and country
. These are sufficient for the API to look up business permissions and make its authorization decision.
public getOrderDeliveryStatus(id: number): Order {const order = this.repository.getOrder(id);const role = claimsPrincipal.getRole();'const country = claimsPrincipal.getCountry()const rolePermissions = this.businessPermissions.getPermissionsForRole(role);if (rolePermissions.canViewOrderDeliveryStatus(order.status, country)) {throw notFoundError();}return order;}
Use Policy-Based Authorization
An alternative to implementing authorization in code is to send the access token to an Entitlement Management System, such as Open Policy Agent or Axiomatics. Doing so provides a mechanism that enables you to centrally govern secure access to sensitive data. An example end-to-end flow might work like this:
- You define central authorization policy documents to enforce particular authorization rules.
- You distribute the policies to each API, which acts as a policy execution point (PEP).
- To make some or all of its authorization decisions, the API calls a policy decision point (PDP).
- The access token serves as a policy information point (PIP) and provides claims to the PDP.
- The PDP writes audit logs that record all security decisions along with contextual data.
- You use log aggregation to ship audit logs so that you can govern security after the event.
The policy decision point is often a small component that you deploy with each API, such as a sidecar container. Using policy-based authorization ensures consistent authorization across multiple APIs, even when they use different technology stacks.
Auditing API Access
Even if you don't use policy-based authorization, consider using its auditing approach. Your API can use a logging framework to write an audit log entry every time it makes an authorization decision. You can combine that with the authorization server's own audit logs for authentication events and token issuance. You can then aggregate all logs to provide visibility of your API-related security to stakeholders.
Claims Best Practices Summary
Use the following guidelines when you design claims to secure your APIs:
- Use claims to lock down access tokens and reduce their privileges, e.g. to a single user's resources.
- Use claims to deliver secure values that APIs need for their authorization.
- Issue familiar business identities to access tokens so that your APIs can associate users to resources.
- Enforce claims-based authorization on every API request to verify all applicable business rules.
- Keep claims stable to avoid claims explosion.
- Manage volatile business permissions in your APIs and not the authorization server.
- Use dynamic claims to convey runtime security context to APIs in high-security use cases.
- Use policy-based authorization when you want to centrally govern API access.
- Write audit logs containing API authorization decisions to provide visibility of your API security.
Custom Claims Tutorial
See the Implementing Custom Claims tutorial and video, for a walkthrough on how to take full control over claims issuance when you use the Curity Identity Server.
Conclusion
Claims are values asserted by your authorization server and issued to access tokens. Your APIs trust the values they receive and use them for authorization. You must apply claims-based authorization and verify all of the applicable business rules on every API request.
When you use a token-based architecture you outsource most of the complex security from your APIs and provide them with straightforward ways to implement authorization. The architecture is extensible and you can scale it to large API platforms.
Gary Archer
Product Marketing Engineer at Curity
Join our Newsletter
Get the latest on identity management, API Security and authentication straight to your inbox.
Start Free Trial
Try the Curity Identity Server for Free. Get up and running in 10 minutes.
Start Free Trial