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)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
Topics
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:
-
Username and group claims with a
cognito:
prefix -
Custom user attributes with a
custom: prefix
-
Custom claims added at runtime
-
OIDC standard claims like
sub
andemail
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
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 toa.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 exampleMyCorp::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:
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