Skip to content

Instantly share code, notes, and snippets.

@daaru00
Last active July 12, 2023 12:45
Show Gist options
  • Select an option

  • Save daaru00/85bbaf6d9b92b00f88bd00e960bbaefa to your computer and use it in GitHub Desktop.

Select an option

Save daaru00/85bbaf6d9b92b00f88bd00e960bbaefa to your computer and use it in GitHub Desktop.
Lambda authorizer to check OAuth2 authorization token
const https = require('https');
const jose = require('node-jose');
const region = process.env.COGNITO_REGION;
const userpool_id = process.env.COGNITO_USER_POOL_ID;
const app_client_id = process.env.COGNITO_CLIENT_ID;
const keys_url = 'https://cognito-idp.' + region + '.amazonaws.com/' + userpool_id + '/.well-known/jwks.json';
/**
* Lambda handler
*
* @param {*} event
*/
exports.handler = async (event) => {
const token = event.authorizationToken.replace('Bearer ', '');
console.log('token: ', token);
if (token == undefined || token.toString().trim().length == 0) {
throw new Error('Unauthorized, Authorization token not found');
}
// get the kid from the headers prior to verification
let header = jose.util.base64url.decode(token.split('.')[0]);
header = JSON.parse(header);
const kid = header.kid;
console.log('kid: ', kid);
// download the public keys
const key = await retrivePublicKey(keys_url, kid);
// construct the public key
const publicKey = await jose.JWK.asKey(key);
// verify the signature
const verifyResponse = await jose.JWS.createVerify(publicKey).verify(token);
// now we can use the claims
const claims = JSON.parse(verifyResponse.payload);
console.log('claims: ', claims)
// additionally we can verify the token expiration
current_ts = Math.floor(new Date() / 1000);
if (current_ts > claims.exp) {
throw new Error('Unauthorized: Token is expired');
}
// and verify the Audience
if (claims.client_id != app_client_id) {
throw new Error('Unauthorized: Token was not issued for this audience');
}
// if everything is ok allow user and return claims
return generatePolicy('user', 'Allow', event.methodArn, claims);
}
/**
* Help function to retrive public key
*
* @param {String} url
* @param {String} kid
*/
function retrivePublicKey(url, kid) {
return new Promise((resolve, reject) => {
https.get(url, (response) => {
if (response.statusCode == 200) {
let chunk = '';
response.on('data', (body) => {
chunk += body;
})
response.on('end', () => {
const body = JSON.parse(chunk);
const keyFound = body.keys.find((key) => {
return key.kid == kid;
})
if (keyFound === undefined) {
reject('Unauthorized: Public key not recognized');
} else {
resolve(keyFound);
}
})
}else{
console.error(response)
reject('Internal error: Cannot verifiy using jwks')
}
})
})
}
/**
* Help function to generate an IAM policy
*
* @param {String} principalId
* @param {String} effect (Allow|Deny)
* @param {String} resource
* @param {*} context
*/
function generatePolicy(principalId, effect, resource, context) {
var authResponse = {};
authResponse.principalId = principalId;
if (effect && resource) {
var policyDocument = {};
policyDocument.Version = '2012-10-17';
policyDocument.Statement = [];
var statementOne = {};
statementOne.Action = 'execute-api:Invoke';
statementOne.Effect = effect;
statementOne.Resource = resource;
policyDocument.Statement[0] = statementOne;
authResponse.policyDocument = policyDocument;
}
// Optional output with custom properties of the String, Number or Boolean type.
authResponse.context = context;
return authResponse;
}
service: ProtectAPI
provider:
name: aws
runtime: nodejs8.10
stage: dev
region: eu-west-1
functions:
app:
handler: app.handler
events:
- http:
path: /my-protected-path
method: GET
authorizer:
name: authorizer
resultTtlInSeconds: 0
identitySource: method.request.header.Authorization
type: token
authorizer:
handler: authorizer.handler
environment:
COGNITO_REGION: ${opt:region, self:provider.region}
COGNITO_USER_POOL_ID:
Ref: UserPool
COGNITO_CLIENT_ID:
Ref: UserPoolClient
resources:
Resources:
UserPool:
Type: "AWS::Cognito::UserPool"
Properties:
UserPoolName: "${self:service}-user-pool"
AutoVerifiedAttributes:
- "email"
Schema:
- Name: name
AttributeDataType: String
Mutable: true
Required: true
- Name: email
AttributeDataType: String
Mutable: false
Required: true
UserPoolClient:
Type: "AWS::Cognito::UserPoolClient"
Properties:
ClientName: "${self:service}-client"
GenerateSecret: true
ExplicitAuthFlows:
- ADMIN_NO_SRP_AUTH
UserPoolId:
Ref: "UserPool"
IdentityPool:
Type: "AWS::Cognito::IdentityPool"
Properties:
IdentityPoolName: "${self:service}Identity"
AllowUnauthenticatedIdentities: true
CognitoIdentityProviders:
- ClientId:
Ref: "UserPoolClient"
ProviderName:
"Fn::GetAtt": ["UserPool", "ProviderName"]
IdentityPoolRoleMapping:
Type: "AWS::Cognito::IdentityPoolRoleAttachment"
Properties:
IdentityPoolId:
Ref: "IdentityPool"
Roles:
authenticated:
"Fn::GetAtt": ["CognitoAuthorizedRole", "Arn"]
@leonardoviada

Copy link
Copy Markdown

@daaru00

daaru00 commented Jul 12, 2023

Copy link
Copy Markdown
Author

Hi @leonardoviada,
would definitely be a better solution, this script is older than the JWT parsing library. In fact it would also render node-jose useless, which it would be nice to replace with aws-jwt-verify.

The related generatePolicy function remains valid, if there isn't some new, slightly more convenient library there too.

@leonardoviada

Copy link
Copy Markdown

Seems like that's still the default approach, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment