UNPKG

lambda-oidc-authenticator

Version:

An AWS Lambda function to provide an oidc Custom Authenticator for AWS API Gateway ( tested with key-cloak). Inspired from lambda-auth0-authorizer

145 lines (109 loc) 4.15 kB
'use strict'; // static setup that can be done at load-time var ACCESS_TOKEN_LENGTH = 16; // (apparent) length of an Autho0 access_token // Lambda now supports environment variables - http://docs.aws.amazon.com/lambda/latest/dg/tutorial-env_cli.html // a .env file can be used as a development convenience. Real environment variables can be used in deployment and // will override anything loaded by dotenv. require('dotenv').config(); const KeyCloakCerts = require('get-keycloak-public-key-node6'); //certs path 'http://auth-server/auth/realms/master/protocol/openid-connect/certs' const keyCloakCerts = new KeyCloakCerts(process.env.certsPath); var fs = require('fs'); var Promise = require('bluebird'); Promise.longStackTraces(); Promise.promisifyAll( Object.getPrototypeOf( keyCloakCerts )); ///// TODO : use promises to load these asynchronously ///// return Promise.resolve to return cached values ///// see : http://bluebirdjs.com/docs/api/promise.method.html var policyDocumentFilename = 'policyDocument.json'; var policyDocument; try { policyDocument = JSON.parse(fs.readFileSync(__dirname + '/' + policyDocumentFilename, 'utf8')); } catch (e) { if (e.code === 'ENOENT') { console.error('Expected ' + policyDocumentFilename + ' to be included in Lambda deployment package'); // fallthrough } throw e; } var jwt = require('jsonwebtoken'); // extract and return the Bearer Token from the Lambda event parameters var getToken = function (params) { var token; if (!params.type || params.type !== 'TOKEN') { throw new Error("Expected 'event.type' parameter to have value TOKEN"); } var tokenString = params.authorizationToken; if (!tokenString) { throw new Error("Expected 'event.authorizationToken' parameter to be set"); } var match = tokenString.match(/^Bearer (.*)$/); if (!match || match.length < 2) { throw new Error("Invalid Authorization token - '" + tokenString + "' does not match 'Bearer .*'"); } return match[1]; } var returnUserInfo = function (data) { if (!data) throw new Error('data empty return'); if (data === 'Unauthorized') { throw new Error('Unauthorized') } else { var user = {}; user.name = data.name; user.email = data.email; user.preferred_username = data.preferred_username; user.given_name = data.given_name; user.family_name = data.family_name; user.principalId = getPrincipalId(data) console.log(user); return user } } // extract user_id from the autho0 userInfo and return it for AWS principalId var getPrincipalId = function (userInfo) { if (!userInfo || (!userInfo.email && !userInfo.preferred_username)) { throw new Error("No email returned from authentication service"); } console.log('authentication successful for user ' + (userInfo.email || userInfo.preferred_username)); return userInfo.email || preferred_username; } // return the expected Custom Authorizaer JSON object var getAuthentication = function (userInfo) { return { principalId: userInfo.principalId, policyDocument: policyDocument, context: userInfo } } //verify the signature on the token var verifyToken = function (token) { return new Promise(function (fulfill, reject) { // decode the token without verification to have the kid value const kid = jwt.decode(token, {complete: true}).header.kid; // fetch the PEM Public Key const publicKey = keyCloakCerts.fetch(kid); publicKey.then((key) => { try { // Verify and decode the token const decoded = jwt.verify(token, key); console.log("token verified successfully "); fulfill(decoded); } catch (error) { // Token is not valid reject("invalid token") } } ).catch(() => { // KeyCloak has no Public Key for the specified KID reject("invalid key id"); }); }); }; module.exports.authenticate = function (params) { var token = getToken(params); var getTokenDataPromise; getTokenDataPromise = verifyToken(token); return getTokenDataPromise .then(returnUserInfo) .then(getAuthentication); }