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
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.0serverless-offline version: v6.1.4node.js version: 12.14.1OS: macOS 10.15.4im 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
Authorizationto send the AccessToken.
Together with--noAuththis 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.
Most helpful comment
@turakvlad Since I only need the username, I resorted to parsing the JWT AccessToken like so
This assumes that you use a header
Authorizationto send the AccessToken.Together with
--noAuththis works.Please note that actually verifying the AccessToken is NOT needed anymore if you use the API Gateway Authorizer, e.g.,
All other tokens, e.g., IDTokens, MUST be properly verified!