Amazon API Gateway is a fully managed AWS service that simplifies the process of creating and managing HTTP and REST APIs at any scale. In this post, I will demonstrate how an organization using OneLogin as the identity provider, and using AWS Lambda authorizers to implement a standard token-based authorization scheme for APIs that are deployed using API Gateway. In order to use OneLogin Access Tokens to control access to resources within API Gateway, you will need to define custom authorization code using a Lambda function to “map” token characteristics to API Gateway resources and permissions. Lambda authorizers are the best choice for organizations that use OneLogin as their identity provider, to directly (without federation) control access to resources in API Gateway, or organizations requiring simple to very complex authorization logic beyond the capabilities offered by “native” authorization mechanisms.
Benefits of using OneLogin tokens with API Gateway
Using a Lambda authorizer with OneLogin tokens in API Gateway can provide the following benefits:
- Integration of OneLogin with API Gateway: If your organization has already adopted OneLogin as your identity provider, building a Lambda authorizer allows users to access API Gateway resources by using their OneLogin credentials without having to configure additional services, such as Amazon Cognito. This can be particularly useful if your organization is using the OneLogin for single sign-on (SSO).
- Minimal impact to client applications: If your organization has an application that is already configured to sign in to a OIDC or OAuth2 identity provider and issue requests using tokens, then minimal changes will be required to use this solution with API Gateway and a Lambda authorizer. By using credentials from OneLogin, you can integrate API Gateway resources into your application in the same manner that non-AWS resources are integrated achieving a common authentication/authorization model..
- Flexibility of authorization logic: Lambda authorizers allow for the additional customization of authorization logic, beyond validation and inspection of tokens.

- After a successful login, the OneLogin issues an access token to a client.
- The client issues an HTTP request to API Gateway and includes the access token in the HTTP Authorization header.
- The API Gateway resource forwards the token to the Lambda authorizer.
- The Lambda authorizer validates the token with OneLogin.
- The Lambda authorizer executes the authorization logic and creates an identity management policy.
- API Gateway evaluates the identity management policy against the API Gateway resource that the user requested and either allows or denies the request. If allowed, API Gateway forwards the user request to the API Gateway resource.
- A OneLogin Account: You will need an OIDC Provider configured in your OneLogin account. The Authorizer uses the OneLogin OIDC Access Token which is a JSON Web Tokens (JWT).
- An API Gateway REST API: You will eventually configure this REST API to rely on the Lambda authorizer for access control.
- Select Services, then select API Gateway.
- If you have API gateways already defined Select Create API. If this is your first one skip to step 3.
- Choose a REST API and click Build. Select OK on the popup if this is your first API Gateway.
- In the Create new API section select the radial button for Example API.
- Leave the rest of the settings default and select Import.
Now that you have the PetStore API created we will need to deploy it to a stage. - Select the **Actions** button in the **Resources** window pane. Under the **API Actions** select **Deploy API**.
- For the **Deployment stage** select **[New Stage]**, and give it a new **Stage name** as ‘***dev****’*. Then select **Deploy.**

- Now that the API has been deployed to the ‘dev’ stage we can now test it to make sure it is working properly. From the dev Stage editor screen select the **Invoke URL** for your API.

- By clicking on the **Invoke URL, **this will launch a new tab on your browser to that address. You should be presented with a screen that looks like this if the PetStore API is working correctly.

# Example HTTP Request using a Bearer token\
GET /dev/pets HTTP/1.1\
Host: 3h7vfljsrj.execute-api.us-east-1.amazonaws.com\
Authorization: Bearer eyJraWQiOiJ0ekgtb1Z5eEpPSF82UDk3...}OneLogin Lambda authorizer
When you configure a Lambda authorizer to serve as the authorization source for an API Gateway resource, the Lambda authorizer is invoked by API Gateway before the resource is called. The core functionality of the Lambda authorizer is to generate a well-formed identity management policy that dictates the allowed actions of the user, such as which APIs the user can access. The OneLogin Lambda authorizer will use information in the OneLogin access token to create the identity management policy based on “permissions mapping” documents that you define — I will discuss these permissions mapping documents in greater detail below. After the Lambda authorizer generates an identity management policy, the policy is returned to API Gateway and API Gateway uses it to evaluate whether the user is allowed to invoke the requested API. You can optionally configure a setting in API Gateway to automatically cache the identity management policy so that subsequent API invocations with the same token do not invoke the Lambda authorizer, but instead use the identity management policy that was generated on the last invocation. In this post, you will create a OneLogin Lambda authorizer to receive an OneLogin OIDC access token and validate its authenticity with the token issuer, then implement custom authorization logic to use the scopes present in the token to create an identity management policy that dictates which APIs the user is allowed to access. You will also configure API Gateway to cache the identity management policy that is returned by the Lambda authorizer. These patterns provide the following benefits:
- Leverage OneLogin identity management services: Validating the token with OneLogin allows for consolidated management of services such as token verification, token expiration, and token revocation.
- Cache to improve performance: Caching the token and identity management policy in API Gateway removes the need to call the Lambda authorizer for each invocation. Caching a policy can improve performance; however, this increased performance comes with additional security considerations. These considerations are discussed below.
- Limit access with scopes: Using the scopes present in the access token, along with custom authorization logic, to generate an identity management policy and limit resource access is a familiar OIDC/OAuth practice and serves as a good example of customizable authentication logic. Refer to Defining Scopes for more information on OAuth scopes and how they are typically used to control resource access.
# LambdaTokenEventPayload
{
type: 'TOKEN',
methodArn: 'arn:aws:execute-api:us-east-1:9298365...',
authorizationToken: 'Bearer byJraWeIEi0J0ekgt...'
}Upon receiving this event, the OneLogin Lambda authorizer wil decode the token and retrieve the ‘kid’ then issue an HTTP GET request to your identity provider to retrieve the certificate and validate the signature on the token, then use the scopes present in the OneLogin access token along with a permissions mapping document to generate and return an identity management policy that contains the allowed actions of the user within API Gateway. Lambda authorizers can be written in any Lambda-supported language but we will use Node JS for this example. The OneLogin Lambda authorizer code in this post uses a static permissions mapping document. This document is represented by apiPermissions. The static document contains the ARN of the deployed API, the API Gateway stage, the API resource, the HTTP method, and the allowed token scope. The Lambda authorizer then generates an identity management policy by evaluating the scopes present in the OneLogin access token against those present in the document. In future posts we will explore complex or highly dynamic permissions, and we will decouple the Lambda authorizer from the static permissions mapping document and export it toAmazon S3 Buckets or Amazon DynamoDB for simplified management. The fragment below shows an example permissions mapping. This mapping restricts access by requiring that users issuing ANY HTTP requests to the ARN arn:aws:execute-api:us-east-1: The logic to create the identity management policy can be found in the generateIAMPolicy() method of the Lambda function. This method serves as a good general example of the customization possible in Lambda authorizers. While the method in the example relies solely on access token scopes, you can also use additional information such as request context, user information, source IP address, user agents, and so on, to generate the returned identity management policy. Upon invocation, the Lambda authorizer below performs the following procedure: It is important to note that the Lambda authorizer above is not considering the method or resource that the user is requesting. This is because you want to generate a complete identity management policy that contains all the API permissions for the user, instead of a policy that only contains allow/deny for the requested resource. By generating a complete policy, this policy can be cached by API Gateway and used if the user invokes a different API while the policy is still in the cache. Caching the policy can reduce API latency from the user perspective, as well as the total amount of Lambda invocations; however, it can also increase vulnerability to Replay Attacks and acceptance of expired/revoked tokens. Shorter cache lifetimes introduce more latency to API calls (that is, the OneLogin Lambda authorizer must be called more frequently), while longer cache lifetimes introduce the possibility of a token expiring or being revoked by the identity provider, but still being used to return a valid identity management policy. For example, the following scenario is possible when caching tokens in API Gateway: This represents a regular expression for validating that tokens match JWT format (more below). This configuration passes the **token event payload **mentioned above to your Lambda authorizer, and is necessary since you are using tokens (Token Event Payload) for authentication, rather than request parameters (Request Event Payload). In this solution, the token source is the Authorization header of the HTTP request. If you know the expected format of your token, you can include a regular expression in the Token Validation field, which automatically rejects any request that does not match the regular expression. Token validators are not mandatory. This example assumes the token is a JWT. Here, you can also configure how long the token/policy pair will be cached in API Gateway. This example enables caching with a TTL of 300 seconds. In this solution, you leave the Lambda Invoke Role field empty. This field is used to provide an IAM role that allows API Gateway to execute the Lambda authorizer. If left blank, API Gateway configures a default resource-based policy that allows it to invoke the Lambda authorizer. The final step is to point your API Gateway resource to your Lambda authorizer. Select the configured API Resource and HTTP method. The following JSON object represents the decoded JWT payload used in the previous example. The JSON object captures the token scopes in scp, and you can see that the token contained the email scope. (Note: Replace If you provide a token that is expired, is invalid, or that does not contain the email scope, then you are not able to access the resource. The following example shows a request to your API Gateway resource with a valid token that does not contain the email scope. In this example, the Lambda authorizer rejects the request. The following JSON object represents the decoded JWT payload used in the above example; it does not include the openid scope. If you provide no token, or you provide a token not matching the provided regular expression, then you are immediately rejected by API Gateway without invoking the Lambda authorizer. The API Gateway only forwards tokens to the Lambda authorizer that have the HTTP Authorization header and pass the token validation regular expression, if a regular expression was provided. If the request does not pass token validation or does not have an HTTP Authorization header, API Gateway rejects it with a default HTTP 401 response. The following example shows how to issue a request to your API Gateway resource using an invalid token that does match the regular expression you configured on your authorizer. In this example, API Gateway rejects your request automatically without invoking the authorizer. These examples demonstrate how your Lambda authorizer allows and denies requests based on the token format and the token content. In this post, you saw how OneLogin Lambda authorizer can be used with API Gateway to implement a token-based authentication scheme using OneLogin OIDC access tokens. Lambda authorizers can provide a number of benefits: Found a problem or a bug? Submit a support ticket. Looking for walkthroughs or how-to guides? Check out our Knowledge Base. Have a product idea or request? Share it in our Ideas Portal.# Example permissions document
{
"arn": "arn:aws:execute-api:us-east-1:<AWSAccountID>:<AWSApiGatewayID>",
"resource": "pets",
"stage": "dev",
"httpVerb": "*",
"scope": "openid"
}
Below is the code for the Lambda Authorizer. This code is just provided for example and discussion in the documentation. Please download the code from the following GitHub Repo: [https://github.com/onelogin/lambda_authorizor_demo](https://github.com/onelogin/lambda_authorizor_demo) Initialize and package the code per the README.md for upload to AWS. /*
* Sample Lambda Authorizer to validate tokens originating from
* OneLogin OIDC Provider and generate an IAM Policy
*/
const jwksClient = require('jwks-rsa');
const jwt = require('jsonwebtoken');
const util = require('util');
const apiPermissions = [
{
"arn": "arn:aws:execute-api:us-east-1:<AWSAccount>:3h7vfljsrj", // NOTE: Replace with your API Gateway API ARN
"resource": "pets", // NOTE: Replace with your API Gateway Resource
"stage": "dev", // NOTE: Replace with your API Gateway Stage
"httpVerb": "GET",
"scope": "openid"
},
{
"arn": "arn:aws:execute-api:us-east-1:<AWSAccount>:3h7vfljsrj", // NOTE: Replace with your API Gateway API ARN
"resource": "pets", // NOTE: Replace with your API Gateway Resource
"stage": "dev", // NOTE: Replace with your API Gateway Stage
"httpVerb": "OPTIONS",
"scope": "email"
},
{
"arn": "arn:aws:execute-api:us-east-1:<AWSAccount>:3h7vfljsrj", // NOTE: Replace with your API Gateway API ARN
"resource": "pets", // NOTE: Replace with your API Gateway Resource
"stage": "dev", // NOTE: Replace with your API Gateway Stage
"httpVerb": "POST",
"scope": "openid"
},
{
"arn": "arn:aws:execute-api:us-east-1:<AWSAccount>:3h7vfljsrj", // NOTE: Replace with your API Gateway API ARN
"resource": "pets/*", // NOTE: Replace with your API Gateway Resource
"stage": "dev", // NOTE: Replace with your API Gateway Stage
"httpVerb": "GET",
"scope": "openid"
},
{
"arn": "arn:aws:execute-api:us-east-1:<AWSAccount>:3h7vfljsrj", // NOTE: Replace with your API Gateway API ARN
"resource": "pets/*", // NOTE: Replace with your API Gateway Resource
"stage": "dev", // NOTE: Replace with your API Gateway Stage
"httpVerb": "OPTIONS",
"scope": "email"
}
];
<strong>var</strong> generatePolicyStatement = function (apiName, apiStage, apiVerb, apiResource, action) {
'use strict'</strong>;
// Generate an IAM policy statement
<strong>var</strong> statement = {};
statement.Action = 'execute-api:Invoke';
statement.Effect = action;
<strong>var</strong> methodArn = apiName + "/" + apiStage + "/" + apiVerb + "/" + apiResource;
statement.Resource = methodArn;
<strong>return</strong> statement;
};
<strong>var</strong> generatePolicy = <strong>function</strong> (principalId, policyStatements) {
<strong> 'use strict'</strong>;
// Generate a fully formed IAM policy
<strong>var</strong> authResponse = {};
authResponse.principalId = principalId;
<strong>var</strong> policyDocument = {};
policyDocument.Version = '2012-10-17';
policyDocument.Statement = policyStatements;
authResponse.policyDocument = policyDocument;
<strong>return</strong> authResponse;
};
<strong>var</strong> verifyAccessToken = <strong>function</strong> (params) {
<strong>'use strict'</strong>;
console.log(params);
<strong>const</strong> token = getToken(params);
<strong>const</strong> decoded = jwt.decode(token, { complete: true });
<strong>if</strong> (!decoded || !decoded.header || !decoded.header.kid) {
<strong>throw</strong><strong>new</strong>Error('invalid token');
}
<strong>const</strong> getSigningKey = util.promisify(client.getSigningKey);
<strong>return</strong> getSigningKey(decoded.header.kid)
.then((key) => {
<strong>const</strong> signingKey = key.publicKey || key.rsaPublicKey;
<strong>return</strong> jwt.verify(token, signingKey, jwtOptions);
})
.then((decoded));
};
<strong>const</strong> getToken = (params) => {
<strong>if</strong> (!params.type || params.type !== 'TOKEN') {
<strong>throw</strong><strong>new</strong>Error('Expected "event.type" parameter to have value "TOKEN"');
}
<strong>const</strong> tokenString = params.authorizationToken;
<strong>if</strong> (!tokenString) {
<strong>throw</strong><strong>new</strong>Error('Expected "event.authorizationToken" parameter to be set');
}
<strong>const</strong> match = tokenString.match(/^Bearer (.*)$/);
<strong>if</strong> (!match || match.length < 2) {
<strong>throw</strong><strong>new</strong>Error(`Invalid Authorization token - ${tokenString} does not match "Bearer .*"`);
}
<strong>return</strong> match[1];
}
<strong>const</strong> client = jwksClient({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 10, // Default value
jwksUri: process.env.JWKS_URI
});
<strong>const</strong> jwtOptions = {
audience: process.env.AUDIENCE,
issuer: process.env.TOKEN_ISSUER
};
<strong>var</strong> generateIAMPolicy = <strong>function</strong> (user,scopeClaims) {
<strong> 'use strict'</strong>;
// Declare empty policy statements array
<strong>var</strong> policyStatements = [];
// Iterate over API Permissions
<strong>for</strong> ( <strong>var</strong> i = 0; i < apiPermissions.length; i++ ) {
// Check if token scopes exist in API Permission
<strong>if</strong> ( scopeClaims.indexOf(apiPermissions[i].scope) > -1 ) {
// User token has appropriate scope, add API permission to policy statements
policyStatements.push(generatePolicyStatement(apiPermissions[i].arn, apiPermissions[i].stage, apiPermissions[i].httpVerb,
apiPermissions[i].resource, "Allow"));
}
}
// Check if no policy statements are generated, if so, create default deny all policy statement
<strong>if</strong> (policyStatements.length === 0) {
<strong>var</strong> policyStatement = generatePolicyStatement("*", "*", "*", "*", "Deny");
policyStatements.push(policyStatement);
}
<strong>return</strong> generatePolicy(user, policyStatements);
};
exports.handler = <strong>async</strong><strong>function</strong>(event, context) {
// Declare Policy
<strong>var</strong> iamPolicy = null;
try {
<strong>var </strong>data = <strong>await </strong>verifyAccessToken(event);
<strong>var </strong>scopeClaims = data.scope;
iamPolicy = generateIAMPolicy(data.sub, scopeClaims);
console.log(JSON.stringify(iamPolicy));
} catch(err) {
console.log(err);
<strong>var</strong> policyStatements = [];
<strong>var</strong> policyStatement = generatePolicyStatement("*", "*", "*", "*", "Deny");
policyStatements.push(policyStatement);
iamPolicy = generatePolicy('user', policyStatements);
}
<strong>return</strong> iamPolicy;
};
Since tokens are not re-validated by the Lambda authorizer or API Gateway once they are placed in the API Gateway cache, long cache lifetimes may also increase susceptibility to Replay Attacks. Longer cache lifetimes and large identity management policies can increase the performance of your application, but must be evaluated against the trade-off of increased exposure to certain security vulnerabilities. ## Deploy the OneLogin Lambda authorizer To deploy the OneLogin Lambda authorizer, you first need to create and deploy a Lambda deployment package containing the function code and dependencies (if applicable). Lambda authorizer functions behave the same as other Lambda functions in terms of deployment and packaging. For more information on packaging and deploying a Lambda function, see[ AWS Lambda Deployment Packages in Node.js](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html). For this example, you should name your Lambda function OneLoginCustomAuthorizer and use a Node.js 12.x runtime environment. After the function is created, add the Lambda authorizer to API Gateway.
/^(Bearer )[a-zA-Z0-9\-_]+?\.[a-zA-Z0-9\-_]+?\.([a-zA-Z0-9\-_]+)$
# Regex matching JWT Bearer Tokens <br />^(Bearer )[a-zA-Z0-9\-_]+?\.[a-zA-Z0-9\-_]+?\.([a-zA-Z0-9\-_]+)$
Deploy the API to an API Gateway stage that matches the stage configured in the Lambda authorizer permissions document (apiPermissions variable). 

## Test the results With the OneLogin Lambda authorizer configured as your authorization source, you are now able to access the resource only if you provide a valid token that contains the openid scope. The following example shows how to issue an HTTP request with curl to your API Gateway resource using a valid token that contains the email scope passed in the HTTP Authorization header. Here, you are able to authenticate and receive an appropriate response from API Gateway. **Note**: It may take up to 30 seconds for the new code to take effect after deployment. 
# HTTP Request (including valid token <strong>with</strong>"openid" scope)
curl -X GET \ 'https://3h7vfljsrj.execute-api.us-east-1.amazonaws.com/dev/pets' \
-H 'Authorization: Bearer eyJhbGciOiJSUzI1...'<br />
[
{
"id": 1,
"type": "dog",
"price": 249.99
},
{
"id": 2,
"type": "cat",
"price": 124.99
},
{
"id": 3,
"type": "fish",
"price": 0.99
}{
"jti": "t235TM5ou_CWdX63kqv8z",
"sub": "113957631",
"iss": "https://<subdomain>.onelogin.com/oidc/2",
"iat": 1608675123,
"exp": 1608678723,
"scope": "openid",
"aud": "6f649880-00fd-0139-adb1-06ee3419f7d3179771"
}<strong>#</strong> HTTP Request (including token without "email" scope)
<strong>$</strong> curl -X GET \
<strong>></strong>'https://3h7vfljsrj.execute-api.us-east-1.amazonaws.com/dev/pets' \
<strong>></strong> -H 'Authorization: Bearer eyJraWQiOiJ0ekgtb1Z5eE...'
{
"Message" : "User is not authorized to access this resource with an explicit deny"
}{
"jti": "t235TM5ou_CWdX63kqv8z",
"sub": "113957631",
"iss": "https://<subdomain>.onelogin.com/oidc/2",
"iat": 1608675123,
"exp": 1608678723,
"scope": "email",
"aud": "6f649880-00fd-0139-adb1-06ee3419f7d3179771"
}<strong>#</strong> HTTP Request (including a token that is not a JWT)
<strong>$</strong> curl -X GET \
<strong>></strong>'https://3h7vfljsrj.execute-api.us-east-1.amazonaws.com/dev/pets' \
<strong>></strong> -H 'Authorization: Bearer ThisIsNotAJWT'
{
"Message" : "Unauthorized"
}Conclusion
For organizations seeking an alternative to Amazon Cognito User Pools and Amazon Cognito identity pools, OneLogin Lambda authorizers can provide complete, secure, and flexible authentication and authorization services to resources deployed with Amazon API Gateway or any API Gateway. To learn more about OneLogin APIs, check out the [OneLogin Documentation Page](https://developers.onelogin.com/quickstart). Have a Question?