Serverless-offline: httpApi authorizer name recognized as function

Created on 24 Apr 2020  路  9Comments  路  Source: dherault/serverless-offline

Bug Report

Current Behavior
While running sls offline start I receive the following error:

offline: Configuring Authorization: /api/endpoint jwtAuthorizer

  Serverless Error ---------------------------------------

  Function "jwtAuthorizer" doesn't exist in this Service

  Get Support --------------------------------------------
     Docs:          docs.serverless.com
     Bugs:          github.com/serverless/serverless/issues
     Issues:        forum.serverless.com

  Your Environment Information ---------------------------
     Operating System:          darwin
     Node Version:              12.14.1
     Framework Version:         1.68.0
     Plugin Version:            3.6.6
     SDK Version:               2.3.0
     Components Version:        2.30.1

Sample Code

  • file: serverless.yml
provider:
    name: aws
    runtime: nodejs12.x
    httpApi:
        authorizers:
            jwtAuthorizer:
              identitySource: $request.header.Authorization
              issuerUrl: jwt-url
              audience:
                - jwt-audience

functions:
    endpoint:
        handler: src/endpoint.handler
        events:
          - httpApi:
              method: GET
              path: /api/endpoint
              authorizer:
                name: jwtAuthorizer

Expected behavior/code

Environment

  • serverless version: 1.68.0
  • serverless-offline version: v6.1.4
  • node.js version: 12.14.1
  • OS: macOS 10.15.4

Most helpful comment

@turakvlad Since I only need the username, I resorted to parsing the JWT AccessToken like so

import * as jsonwebtoken from 'jsonwebtoken';

interface ParsedToken {
  sub: string
  event_id: string
  token_use: string
  scope: string
  auth_time: number,
  iss: string
  exp: number,
  iat: number,
  jti: string
  client_id: string
  username: string
}

const parsedToken = (jsonwebtoken.decode(getAccessToken(event)) as ParsedToken)
const username = parsedToken.username;

export function getAccessToken(event: APIGatewayProxyEvent): string {
  return event.headers.authorization || event.headers.Authorization;
}

This assumes that you use a header Authorization to send the AccessToken.
Together with --noAuth this works.

Please note that actually verifying the AccessToken is NOT needed anymore if you use the API Gateway Authorizer, e.g.,

httpApi: {
  payload: '2.0',
  cors: true,
  authorizers: {
    serviceAuthorizer: {
      identitySource: '$request.header.Authorization',
      issuerUrl: 'https://cognito-idp.YOUR_REGION.amazonaws.com/YOUR_USERPOOL_ID',
      audience: [
        'YOUR_AUDIENCE'
      ]
    }
  }
}

All other tokens, e.g., IDTokens, MUST be properly verified!

All 9 comments

im having the same issue
is there a workaround?

found the solution
sls offline start --noAuth

found the solution
sls offline start --noAuth

Thank you for posting this :)

The --noAuth hack does not fill the event: APIGatewayProxyEvent correctly, e.g. event.requestContext.authorizer.claims.username is not set.
Are there any solutions integrating a little better with AWS Cognito?

I have the same issue. It would be great to see some good practices for integrating serverless-offline with Cognito.

@davidgengenbach, have you found any solutions? Thanks.

@turakvlad Since I only need the username, I resorted to parsing the JWT AccessToken like so

import * as jsonwebtoken from 'jsonwebtoken';

interface ParsedToken {
  sub: string
  event_id: string
  token_use: string
  scope: string
  auth_time: number,
  iss: string
  exp: number,
  iat: number,
  jti: string
  client_id: string
  username: string
}

const parsedToken = (jsonwebtoken.decode(getAccessToken(event)) as ParsedToken)
const username = parsedToken.username;

export function getAccessToken(event: APIGatewayProxyEvent): string {
  return event.headers.authorization || event.headers.Authorization;
}

This assumes that you use a header Authorization to send the AccessToken.
Together with --noAuth this works.

Please note that actually verifying the AccessToken is NOT needed anymore if you use the API Gateway Authorizer, e.g.,

httpApi: {
  payload: '2.0',
  cors: true,
  authorizers: {
    serviceAuthorizer: {
      identitySource: '$request.header.Authorization',
      issuerUrl: 'https://cognito-idp.YOUR_REGION.amazonaws.com/YOUR_USERPOOL_ID',
      audience: [
        'YOUR_AUDIENCE'
      ]
    }
  }
}

All other tokens, e.g., IDTokens, MUST be properly verified!

@turakvlad Since I only need the username, I resorted to parsing the JWT AccessToken like so

import * as jsonwebtoken from 'jsonwebtoken';

interface ParsedToken {
  sub: string
  event_id: string
  token_use: string
  scope: string
  auth_time: number,
  iss: string
  exp: number,
  iat: number,
  jti: string
  client_id: string
  username: string
}

const parsedToken = (jsonwebtoken.decode(getAccessToken(event)) as ParsedToken)
const username = parsedToken.username;

export function getAccessToken(event: APIGatewayProxyEvent): string {
  return event.headers.authorization || event.headers.Authorization;
}

This assumes that you use a header Authorization to send the AccessToken.
Together with --noAuth this works.

Please note that actually verifying the AccessToken is NOT needed anymore if you use the API Gateway Authorizer, e.g.,

httpApi: {
  payload: '2.0',
  cors: true,
  authorizers: {
    serviceAuthorizer: {
      identitySource: '$request.header.Authorization',
      issuerUrl: 'https://cognito-idp.YOUR_REGION.amazonaws.com/YOUR_USERPOOL_ID',
      audience: [
        'YOUR_AUDIENCE'
      ]
    }
  }
}

All other tokens, e.g., IDTokens, MUST be properly verified!

Hi there, thanks for sharing! However it's not clear to me why ID token must be verified. I use the Oauth2 authorizer of the http-api with ID tokens and it seems to work.. (just wondering if I have a security problem with this configuration). I thought that the API Gateway verified the token on my behalf. Is not the case with ID token?

Another point, since http api does not have a lambda authorizer, where do you verify/decode the token? On your target lambda? Thanks

However it's not clear to me why ID token must be verified. I use the Oauth2 authorizer of the http-api with ID tokens and it seems to work.. (just wondering if I have a security problem with this configuration)

Interesting that it works with ID tokens as well. As far as I know (and I am not a security specialist by any means!), the ID token is only for identification. When you actually want to do requests AS THE USER from your Lambda, you need the access token.
Of course, you could only test whether a user is the person it claims to be (= ID token) and do all requests to the AWS API via Lambda permissions (= the permissions you give in the serverless.yaml).

My comment "All other tokens, e.g., IDTokens, MUST be properly verified!" is meant like: "if you send the ID token to the lambda as well, e.g., via the POST body, you have to verify them as they are NOT verified by the Authorizer".

My verification code, which should work for ID- and Access-Tokens alike, is:

import JwksClient, {CertSigningKey, RsaSigningKey} from 'jwks-rsa';
import * as jsonwebtoken from 'jsonwebtoken';

export const OAUTH_CONFIG = {
  jwt_public_keys: `https://cognito-idp.${REGION}.amazonaws.com/${USER_POOL_ID}/.well-known/jwks.json`,
}


export async function isValidToken(token: string): Promise<boolean> {
  // Adapted from https://www.npmjs.com/package/jsonwebtoken#jwtverifytoken-secretorpublickey-options-callback
  const client = JwksClient({
    jwksUri: OAUTH_CONFIG.jwt_public_keys
  });

  function getKey(header, callback) {
    client.getSigningKey(header.kid, function (_, key) {
      const signingKey = (key as CertSigningKey).publicKey || (key as RsaSigningKey).rsaPublicKey;
      callback(null, signingKey);
    });
  }

  return new Promise<boolean>((resolve, _) => {
    jsonwebtoken.verify(token, getKey, function (err, decoded) {
      if (err) return resolve(false);
      resolve(!!decoded);
    });
  });
}

But to reiterate: if ID tokens work normal without any modifications and the API gateway lets the request through to your Lambda, the ID token is verified automatically. You can easily test this by using an invalid ID token and observe whether you get a 401 Not Authorized.

More information can be found here: https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html
In particular:

The identity token is used to authorize API calls based on identity claims of the signed-in user. The access token is used to authorize API calls based on the custom scopes of specified access-protected resources.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Dong9769 picture Dong9769  路  4Comments

adambiggs picture adambiggs  路  4Comments

Looveh picture Looveh  路  4Comments

ghost picture ghost  路  4Comments

Rafaelsk picture Rafaelsk  路  4Comments