Using Amazon Verified Permissions with identity providers - Amazon Verified Permissions

Using Amazon Verified Permissions with identity providers

An identity source is a representation of an external identity provider (IdP) in Amazon Verified Permissions. Identity sources provide information from a user who authenticated with an IdP that has a trust relationship with your policy store. When your application makes an authorization request with a token from an identity source, your policy store can make authorization decisions from user properties and access permissions. You can add an Amazon Cognito user pool or a custom OpenID Connect (OIDC) IdP as your identity source.

You can use OpenID Connect (OIDC) identity providers (IdPs) with Verified Permissions. Your application can generate authorization requests with JSON web tokens (JWTs) generated by an OIDC-compliant identity provider. The user identity in the token is mapped to the principal ID. With ID tokens, Verified Permissions maps attribute claims to principal attributes. With Access tokens, these claims are mapped to context. With both token types, you can map a claim like groups to a principal group, and build policies that evaluate role-based access control (RBAC).

For a step-by-step walkthrough that builds authorization logic for Amazon API Gateway REST APIs using an Amazon Cognito user pool or OIDC identity provider, see Authorize API Gateway APIs using Amazon Verified Permissions with Amazon Cognito or bring your own identity provider on the AWS Security Blog.

Working with Amazon Cognito identity sources

Verified Permissions works closely with Amazon Cognito user pools. Amazon Cognito JWTs have a predictable structure. Verified Permissions recognizes this structure and draws maximum benefit from the information that it contains. For example, you can implement a role-based access control (RBAC) authorization model with either ID tokens or access tokens.

A new Amazon Cognito user pools identity source requires the following information:

  • The AWS Region.

  • The user pool ID.

  • The principal entity type that you want to associate with your identity source, for example MyCorp::User.

  • The principal group entity type that you want to associate with your identity source, for example MyCorp::UserGroup.

  • The client IDs from your user pool that you want to authorize to make requests to your policy store.

Because Verified Permissions only works with Amazon Cognito user pools in the same AWS account, you can't specify an identity source in another account. Verified Permissions sets the entity prefix—the identity-source identifier that you must reference in policies that act on user pool principals—to the ID of your user pool, for example us-west-2_EXAMPLE.

User pool token claims can contain attributes, scopes, groups, client IDs, and custom data. Amazon Cognito JWTs have the ability to include a variety of information that can contribute to authorization decisions in Verified Permissions. These include:

  1. Username and group claims with a cognito: prefix

  2. Custom user attributes with a custom: prefix

  3. Custom claims added at runtime

  4. OIDC standard claims like sub and email

We cover these claims in detail, and how to manage them in Verified Permissions policies, in Mapping identity provider tokens to schema.

Important

Although you can revoke Amazon Cognito tokens before they expire, JWTs are considered to be stateless resources that are self-contained with a signature and validity. Services that conform with the JSON Web Token RFC 7519 are expected to validate tokens remotely and aren't required to validate them with the issuer. This means that it is possible for Verified Permissions to grant access based on a token that was revoked or issued for a user that was later deleted. To mitigate this risk, we recommend that you create your tokens with the shortest possible validity duration and revoke refresh tokens when you want to remove authorization to continue a user's session.

Cedar policies for user pool identity sources in Verified Permissions use a special syntax for claim names that contain characters other than alphanumeric and underscore (_). This includes user pool prefix claims that contain a : character, like cognito:username and custom:department. To write a policy condition that references the cognito:username or custom:department claim, write them as principal["cognito:username"] and principal["custom:department"], respectively.

Note

If a token contains a claim with a cognito: or custom: prefix and a claim name with the literal value cognito or custom, an authorization request with IsAuthorizedWithToken will fail with a ValidationException.

This following example shows how you might create a policy that references some of the Amazon Cognito user pools claims associated with a principal. Note that the principal ID takes the form of "<userpool-id>|<sub>"

permit( principal == ExampleCo::User::"us-east-1_example|a1b2c3d4-5678-90ab-cdef-EXAMPLE11111", action, resource == ExampleCo::Photo::"VacationPhoto94.jpg" ) when { principal["cognito:username"]) == "alice" && principal["custom:department"]) == "Finance" };

For more information about mapping claims, see Mapping ID tokens to schema. For more information about authorization for Amazon Cognito users, see Authorization with Amazon Verified Permissions in the Amazon Cognito Developer Guide.

Working with OIDC identity sources

You can also configure any compliant OpenID Connect (OIDC) IdP as the identity source of a policy store. OIDC providers are similar to Amazon Cognito user pools: they produce JWTs as the product of authentication. To add an OIDC provider, you must provide an issuer URL

A new OIDC identity source requires the following information:

  • The issuer URL. Verified Permissions must be able to discover a .well-known/openid-configuration endpoint at this URL.

  • CNAME records that don't include wild cards. For example, a.example.com can't be mapped to *.example.net. Conversely, *.example.com can't be mapped to a.example.net.

  • The token type that you want to use in authorization requests. In this case, you chose Identity token.

  • The user entity type that you want to associate with your identity source, for example MyCorp::User.

  • The group entity type that you want to associate with your identity source, for example MyCorp::UserGroup.

  • An example ID token, or a definition of the claims in the ID token.

  • The prefix that you want to apply to user and group entity IDs. In the CLI and API, you can choose this prefix. In policy stores that you create with the Set up with API Gateway and an identity provider or Guided setup option, Verified Permissions assigns a prefix of the issuer name minus https://, for example MyCorp::User::"auth.example.com|a1b2c3d4-5678-90ab-cdef-EXAMPLE11111".

For more information about using API operations to authorize requests from OIDC sources, see Available API operations for authorization.

This following example shows how you might create a policy that permits access to year-end reports for employees in the accounting department, have a confidential classification, and aren't in a satellite office. Verified Permissions derives these attributes from the claims in the principal's ID token.

permit( principal in MyCorp::UserGroup::"MyOIDCProvider|Accounting", action, resource in MyCorp::Folder::"YearEnd2024" ) when { principal.jobClassification == "Confidential" && !(principal.location like "SatelliteOffice*") };

Client and audience validation

When you add an identity source to a policy store, Verified Permissions has configuration options that verify that ID and access tokens are being used as intended. This validation happens in the processing of IsAuthorizedWithToken and BatchIsAuthorizedWithToken API requests. The behavior differs between ID and access tokens, and between Amazon Cognito and OIDC identity sources. With Amazon Cognito user pools providers, Verified Permissions can validate the client ID in both ID and access tokens. With OIDC providers, Verified Permissions can validate the client ID in ID tokens, and the audience in access tokens.

A client ID is an identifier associated with the identity provider instance that your application uses, for example 1example23456789. An audience is a URL path associated with the intended relying party, or destination, of the access token, for example https://mytoken.example.com. When using access tokens, the aud claim is always associated with the audience.

Verified Permissions performs identity source audience and client validation as follows:

Amazon Cognito

Amazon Cognito ID tokens have an aud claim that contains the app client ID. Access tokens have a client_id claim that also contains the app client ID.

When you enter one or more values for Client application validation in your identity source, Verified Permissions compares this list of app client IDs to the ID token aud claim or the access token client_id claim. Verified Permissions doesn't validate a relying-party audience URL for Amazon Cognito identity sources.

OIDC

OIDC ID tokens have an aud claim that contains a list of client IDs. Access tokens have an aud claim that contains the audience URL for the token. Access tokens also have a client_id claim that contains the intended client ID.

You can enter one or more values for Audience validation with an OIDC provider. When you choose a Token type of ID token, Verified Permissions validates the client ID, checking that at least one member of the client IDs in the aud claim matches an audience validation value.

Verified Permissions validates the audience for access tokens, checking that the aud claim matches an audience validation value. This access-token value primarily comes from the aud claim but can come from the cid or client_id claim if no aud claim exists. Check with your IdP for the correct audience claim and format.

An example ID token audience validation value is 1example23456789.

An example access token audience validation value is https://myapplication.example.com.

Client-side authorization for JWTs

You might want to process JSON web tokens in your application and pass their claims to Verified Permissions without using a policy store identity source. You can extract your entity attributes from a JSON Web Token (JWT) and parse it into Verified Permissions.

This example shows how you might call Verified Permissions from an OIDC IdP.¹

async function authorizeUsingJwtToken(jwtToken) { const payload = await verifier.verify(jwtToken); let principalEntity = { entityType: "PhotoFlash::User", // the application needs to fill in the relevant user type entityId: payload["sub"], // the application need to use the claim that represents the user-id }; let resourceEntity = { entityType: "PhotoFlash::Photo", //the application needs to fill in the relevant resource type entityId: "jane_photo_123.jpg", // the application needs to fill in the relevant resource id }; let action = { actionType: "PhotoFlash::Action", //the application needs to fill in the relevant action id actionId: "GetPhoto", //the application needs to fill in the relevant action type }; let entities = { entityList: [], }; entities.entityList.push(...getUserEntitiesFromToken(payload)); let policyStoreId = "PSEXAMPLEabcdefg111111"; // set your own policy store id const authResult = await client .isAuthorized({ policyStoreId: policyStoreId, principal: principalEntity, resource: resourceEntity, action: action, entities, }) .promise(); return authResult; } function getUserEntitiesFromToken(payload) { let attributes = {}; let claimsNotPassedInEntities = ['aud', 'sub', 'exp', 'jti', 'iss']; Object.entries(payload).forEach(([key, value]) => { if (claimsNotPassedInEntities.includes(key)) { return; } if (Array.isArray(value)) { var attibuteItem = []; value.forEach((item) => { attibuteItem.push({ string: item, }); }); attributes[key] = { set: attibuteItem, }; } else if (typeof value === 'string') { attributes[key] = { string: value, } } else if (typeof value === 'bigint' || typeof value ==='number') { attributes[key] = { long: value, } } else if (typeof value === 'boolean') { attributes[key] = { boolean: value, } } }); let entityItem = { attributes: attributes, identifier: { entityType: "PhotoFlash::User", entityId: payload["sub"], // the application needs to use the claim that represents the user-id } }; return [entityItem]; }

¹ This code example uses the aws-jwt-verify library for verifying JWTs signed by OIDC-compatible IdPs.