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).

Note

Verified Permissions makes authorization decisions based on information from an IdP token but doesn't interact directly with the IdP in any way.

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. In this case, you would reference a user in that user pool with ID a1b2c3d4-5678-90ab-cdef-EXAMPLE22222 as us-west-2_EXAMPLE|a1b2c3d4-5678-90ab-cdef-EXAMPLE22222

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. For more information, see Ending user sessions with token revocation

This following example shows how you might create a policy that references some of the Amazon Cognito user pools claims associated with a principal.

permit( principal, action, resource == ExampleCo::Photo::"VacationPhoto94.jpg" ) when { principal["cognito:username"]) == "alice" && principal["custom:department"]) == "Finance" };

This following example shows how you might create a policy that references a principal that's a user in a Cognito user pool. 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" );

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.

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.

Note that when referencing a group in the principal, you must use the in operator for the policy to be evaluated correctly.

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 client IDs, such as 1example23456789.

OIDC Access tokens have an aud claim that contains the audience URL for the token, such as https://myapplication.example.com, and a client_id claim that contains client IDs, such as 1example23456789.

When setting up your policy store, enter one or more values for Audience validation that your policy store with use to validate the audience of a token.

  • ID tokens – Verified Permissions validates the client ID by checking that at least one member of the client IDs in the aud claim matches an audience validation value.

  • Access tokens – Verified Permissions validates the audience by checking that the URL in the aud claim matches an audience validation value. If no aud claim exists, the audience can be validated using the cid or client_id claims. Check with your identity provider for the correct audience claim and format.

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 application using a JWT.¹

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.