Hello I was implementing my first secure APIs and...
# help
p
Hello I was implementing my first secure APIs and implemented a custom authorizer using Auth0. During the process, I realized there's a built-in JWT authorizer supported by HTTP API type (
httpApi
event type in sls), which I also didn't know. So, using
httpApi
I'm able to define the required
scopes
for my function. Is there a way to do something similar using the regular
http
event type and a custom authorizer? Also, what do you usually use in terms of AGW API type: REST or HTTP? I saw this page but would like to know about real experiences... Thanks
f
I haven’t used JWT with REST api, I think it is possible.
I’d recommend using HTTP API when possible. It doens’t support x-ray yet, but I don’t think that’s a bigger issue since u can still turn on x-ray in your Lambda functions.
Can I go into more details if you share your have a specific concern with HTTP API. But in general go for it.
p
Thanks, Frank. I don't have any concerns... Actually, I liked it a lot. I'm asking just because it's new to me 😄
btw... the code to validate the JWT for a REST api is quite simple. Adapted this from the auth0 example
Copy code
import util from 'util';
import jwksClient from 'jwks-rsa';
import jwt from 'jsonwebtoken';
import debug from '../../../common/lib/debug';
import { handler } from "../../../common/lib/handler";

const getPolicyDocument = (effect, resource) => {
  const policyDocument = {
    Version: '2012-10-17', // default version
    Statement: [{
      Action: 'execute-api:Invoke', // default action
      Effect: effect,
      Resource: resource,
    }]
  };
  return policyDocument;
};

// extract and return the Bearer Token from the Lambda event parameters
const getToken = (params) => {
  if (!params.type || params.type !== 'TOKEN') {
    throw new Error('Expected "event.type" parameter to have value "TOKEN"');
  }

  const tokenString = params.authorizationToken;
  if (!tokenString) {
    throw new Error('Expected "event.authorizationToken" parameter to be set');
  }

  const match = tokenString.match(/^Bearer (.*)$/);
  if (!match || match.length < 2) {
    throw new Error(`Invalid Authorization token - ${tokenString} does not match "Bearer .*"`);
  }
  return match[1];
};

const jwtOptions = {
  audience: process.env.AUDIENCE,
  issuer: process.env.TOKEN_ISSUER
};

const client = jwksClient({
  cache: true,
  rateLimit: true,
  jwksRequestsPerMinute: 10, // Default value
  jwksUri: process.env.JWKS_URI
});

const getSigningKey = util.promisify(client.getSigningKey);

const authenticate = async (params) => {
  const token = getToken(params);

  const decoded = jwt.decode(token, { complete: true });
  if (!decoded || !decoded.header || !decoded.header.kid) {
    throw new Error('invalid token');
  }

  const key = await getSigningKey(decoded.header.kid);
  const signingKey = key.publicKey || key.rsaPublicKey;
  const verified = jwt.verify(token, signingKey, jwtOptions);

  return {
    principalId: verified.sub,
    policyDocument: getPolicyDocument('Allow', params.methodArn),
    context: { scope: verified.scope }
  };
};

export const main = handler(async (event) => {
  try {
    return await authenticate(event);
  } catch (e) {
    debug(e);
    return {
      policyDocument: getPolicyDocument('Deny', event.methodArn),
    };
  }
});
But I will definitely follow your advice and use HTTP api when possible, so I don't need to handle the validation in my code and also have the benefit to be able to set the required scopes by function.
r
One tiny thing, I think it makes sense to promisify client.getSigningKey outside of the method so it only needs to happen once and can be reused for subsequent warm invocations.