New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add optional JWT authorizer for HttpApi events #1022
Conversation
export default function createAuthScheme(jwtOptions) { | ||
const authorizerName = jwtOptions.name | ||
|
||
const identitySourceMatch = /^\$request.header.((?:\w+-?)+\w+)$/.exec( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we get a comment roughly describing what this does for the people who are terrible with regex (like myself)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I struggle with regex too - here's the explanation from regex 101
/
^$request.header.((?:\w+-?)+\w+)$
/
^ asserts position at start of the string
$ matches the character $ literally (case sensitive)
request matches the characters request literally (case sensitive)
. matches any character (except for line terminators)
header matches the characters header literally (case sensitive)
. matches any character (except for line terminators)
1st Capturing Group ((?:\w+-?)+\w+)
Non-capturing group (?:\w+-?)+
- Quantifier — Matches between one and unlimited times, as many times as possible, giving back as needed (greedy)
\w+
matches any word character (equal to [a-zA-Z0-9_])
-?
matches the character - literally (case sensitive)
\w+
matches any word character (equal to [a-zA-Z0-9_])
$ asserts position at the end of the string, or before the line terminator right at the end of the string (if any)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect it's parsing the authorization header btw @chardos
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The regex is just copied from src/events/http/createAuthScheme.js
I did a little digging in the git history. Based on this commit:
the regex was modified from just grabbing all characters (the w+
part) to also grabbing hyphens since those are valid characters in a request header.
This doesn't actually parse the auth header. It figures out what the name of the header key is if you have customized the header value that will actually contain the token. That header key is then used when the requests are coming in to grab the auth token from the request.
Hey @qswinson, Thanks for this awesome functionality! I've had a look at the PR, and am ready to approve, as soon as the tests are passing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like it - when are you adding the signature validation? ;)
|
||
// TODO: add code that will actually validate a JWT. | ||
if (!ignoreJWTSignature) { | ||
return buildSuccessResult(null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would it be better to throw an error or return failure result if the user specifies that the signature should not be ignored?
They may be unaware that the signature isn't being validated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you don't set ignoreJWTSignature
you will have to set noAuth
for serverless-offline to run at all for HttpApi events with authorization. The current behavior of serverless-offline is to treat authorization as another lambda. Since there isn't a function mapped to the authorizer, serverless-offline throws an error on startup. My intention with this flag is to make it explicit that while the authorization rules defined in the serverless.yml file are being tested, this is should not be considered valid security.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let me know if you'd like to see the change you suggested. my intention was not to modify the current behavior of the library.
export default function createAuthScheme(jwtOptions) { | ||
const authorizerName = jwtOptions.name | ||
|
||
const identitySourceMatch = /^\$request.header.((?:\w+-?)+\w+)$/.exec( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I struggle with regex too - here's the explanation from regex 101
/
^$request.header.((?:\w+-?)+\w+)$
/
^ asserts position at start of the string
$ matches the character $ literally (case sensitive)
request matches the characters request literally (case sensitive)
. matches any character (except for line terminators)
header matches the characters header literally (case sensitive)
. matches any character (except for line terminators)
1st Capturing Group ((?:\w+-?)+\w+)
Non-capturing group (?:\w+-?)+
- Quantifier — Matches between one and unlimited times, as many times as possible, giving back as needed (greedy)
\w+
matches any word character (equal to [a-zA-Z0-9_])
-?
matches the character - literally (case sensitive)
\w+
matches any word character (equal to [a-zA-Z0-9_])
$ asserts position at the end of the string, or before the line terminator right at the end of the string (if any)
export default function createAuthScheme(jwtOptions) { | ||
const authorizerName = jwtOptions.name | ||
|
||
const identitySourceMatch = /^\$request.header.((?:\w+-?)+\w+)$/.exec( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect it's parsing the authorization header btw @chardos
const { req } = request.raw | ||
let jwtToken = req.headers[identityHeader] | ||
if (jwtToken && jwtToken.split(' ')[0] === 'Bearer') { | ||
;[, jwtToken] = jwtToken.split(' ') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using array destructuring is a clever way of leveraging the output of split
path: '/dev/user2', | ||
status: 403, | ||
}, | ||
].forEach(({ description, expected, jwt, path, status }) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Neat trick
I realize I still have a failing test. I've spent a little time and am not sure what is going on. This test is also failing when I run the test suite on the master branch on my local machine. If someone could point me in the right direction, I should have time this weekend to look at it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@chardos what do you think?
If the documentation is updated I think we are good to go.
Got my friend @trevorgk who specialises in Auth to take a look (since I've got no idea), so I'm confident this PR is as awesome as it looks 👍 |
v6.5.0 |
This adds basic checking of JWTs based on authorizers defined in the serverless file for HttpApi events. It does not validate the signature of the JWT, therefore an opt-in flag is required to enable this feature. This is only meant for local testing of locally issued JWTs. It is not meant for consuming and granting access based on externally generate JWTs due to the security concerns.