-
Notifications
You must be signed in to change notification settings - Fork 402
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
[SDK-4319] Add support for Edge runtime #1269
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎ 1 Ignored Deployment
|
@@ -41,28 +41,52 @@ export default function Nav() { | |||
<a>Profile</a> | |||
</Link> | |||
</li> | |||
<li> | |||
<Link href="/edge-profile" legacyBehavior> |
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.
Is legacyBehavior
still needed here (and in other links)?
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 was just moving this over from the old example app, I could change them - but I'd rather do that in another PR
return auth0LogoutUrl.toString(); | ||
} | ||
if (!as.end_session_endpoint) { | ||
throw new Error('no rp inititated logout'); |
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.
Should this error have a more specific type?
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.
None of our configuration errors have a type, they just need to tell the developer they've configured the SDK wrong
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.
Should this error in particular be a little more descriptive then? If I were a developer new to identity and got that error, I think I'd be confused.
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.
Sure - I can improve the message
if (clientPrivateKey && !(clientPrivateKey instanceof CryptoKey)) { | ||
clientPrivateKey = await jose.importPKCS8<CryptoKey>(clientPrivateKey, clientAssertionSigningAlg || 'RS256'); | ||
} | ||
const response = await oauth.authorizationCodeGrantRequest( |
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.
Can this call throw an exception? If so, should it be caught, wrapped, and rethrown?
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.
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.
In the node handler we're wrapping the errors in our own, more specific error types:
throw new IdentityProviderError(err); |
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.
authorizationCodeGrantRequest
doesn't throw an IdentityProvider
error (one with an error
/ error_description
that comes from the IDP) it just returns a fetch response (or throws config errors like, "you forgot the response_type
")
processAuthorizationCodeOpenIDResponse
returns an IDP error (not throws) which is thrown on L158
} | ||
); | ||
|
||
const result = await oauth.processAuthorizationCodeOpenIDResponse( |
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.
Same as above.
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.
|
||
async userinfo(accessToken: string): Promise<Record<string, unknown>> { | ||
const [as, client] = await this.getClient(); | ||
const response = await oauth.userInfoRequest(as, client, accessToken, this.httpOptions()); |
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.
Can this call throw an exception? If so, should it be caught, wrapped, and rethrown?
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.
This get's caught here /~https://github.com/auth0/nextjs-auth0/blob/edge-auth-handlers/src/handlers/profile.ts#L237
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.
In the node handler we're wrapping the errors in our own, more specific error types:
throw new IdentityProviderError(err); |
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.
Not for userinfo
- but yeah - fair enough, I'll create a UserInfoError
and add it to the node and edge clients
|
||
async refresh(refreshToken: string, extras: { exchangeBody: Record<string, any> }): Promise<TokenEndpointResponse> { | ||
const [as, client] = await this.getClient(); | ||
const res = await oauth.refreshTokenGrantRequest(as, client, refreshToken, { |
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.
Can this call throw an exception? If so, should it be caught, wrapped, and rethrown?
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.
This should throw an AccessTokenError
will fix
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.
This seems to be missing in 0bb1e4a
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.
Ah yeah, was going to say - this is the same as #1269 (comment) the error from the idp get's thrown from processRefreshTokenResponse
additionalParameters: extras.exchangeBody, | ||
...this.httpOptions() | ||
}); | ||
const result = await oauth.processRefreshTokenResponse(as, client, res); |
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.
Same as above.
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.
This doesn't throw, if there's an error oauth.isOAuth2Error(result)
is true
(which is caught below)
throw new Error(`response_type should be one of ${validResponseTypes.join(', ')}`); | ||
} | ||
if (!/\bopenid\b/.test(authParams.scope as string)) { | ||
throw new Error('scope should contain "openid"'); |
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.
Should these errors have a more specific type?
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.
This is a configuration error too - we don't have a type for these. We could look at changing that, but it would be a lot of changes for not much/any benefit.
src/edge.ts
Outdated
@@ -28,39 +29,57 @@ const genId = () => { | |||
.join(''); | |||
}; | |||
|
|||
function getInstance(params?: ConfigParameters): Auth0Edge { | |||
const telemetry = { name: 'nextjs-auth0', version }; |
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.
Should this be moved to some common helper, as it's also used in
Line 26 in 141ec7b
const telemetry = { name: 'nextjs-auth0', version }; |
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.
Yeah, I can move this to shared.ts
src/utils/errors.ts
Outdated
@@ -110,7 +108,7 @@ export class AccessTokenError extends AuthError { | |||
/** | |||
* @ignore | |||
*/ | |||
export type HandlerErrorCause = Error | AuthError | HttpError; | |||
export type HandlerErrorCause = Error | AuthError | (Error & { status: number; statusCode: number }); |
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.
Should Error & { status: number; statusCode: number }
be its own type?
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.
Sure - I'll make an HttpError
type
@@ -436,60 +436,6 @@ describe('callback', () => { | |||
expect(header.alg).toEqual('RS256'); | |||
}); | |||
|
|||
it('should use client secret jwt on token endpoint', async () => { |
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.
Apologies if this is something obvious, why is this test being removed?
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.
Auth0 doesn't support client secret jwt and the edge client doesn't so I've removed support
tests/fixtures/app-router-helpers.ts
Outdated
} else { | ||
return nodeInitAuth0(config); | ||
} |
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.
} else { | |
return nodeInitAuth0(config); | |
} | |
} | |
return nodeInitAuth0(config); |
if (this.as) { | ||
return [this.as, this.client as oauth.Client]; | ||
} |
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.
Not sure how long-lived instances in the Edge runtime can be (apparently there's still some notion of "cold-start"). If these were short-lived, wouldn't we be calling the discovery endpoint a lot?
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.
We can't do any better than keeping them in memory 🤷 (unless Next.js has some built in distributed cache I haven't heard of)
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 have an option for not using the discovery endpoint?
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.
After all this is an Auth0-specific SDK.
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.
Or maybe not using it by default on the edge client.
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.
Asking because serverless !== edge. As Vercel's architect mentions, "Edge Functions bring another set of trade-offs into the mix".
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.
What was enough/serviceable with serverless might not be with edge functions.
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.
In any case, this probably warrants more investigation and testing with the edge runtime to find out if something more needs to be done.
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.
Since edge functions apparently use Cloudflare Workers, I guess it comes down to the differences between Cloudflare Workers and AWS Lambda.
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.
This SDK is agnostic to a particular edge vendor (Vercel/AWS/Azure/CloudFlare/GCP) - but sure, we can look into how their memory persistence differs between the 2 runtimes. I guess at the very least, Edge functions will be more populous given that their are more Edge locations - so I guess the probability of hitting the same box is less. But on the other hand, the Discovery is served from a CDN, so it's not a huge cost.
describe('login handler (page router)', () => { | ||
afterEach(teardown); | ||
|
||
test('should create a state', async () => { |
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.
test('should create a state', async () => { | |
test('should create a state, nonce, and code verifier', async () => { |
Thanks for the review @Widcket 🙏 |
📋 Changes
Have added support for the Edge runtime (in the app directory) to the auth handlers and session helpers:
Auth Handlers
handleAuth
handleLogin
handleCallback
handleLogout
handleProfile
Session Helpers
withApiAuthRequired
withPageAuthRequired
updateSession
getAccessToken
(
getSession
is already supported)We now use 2 OIDC clients:
Using Jest "projects" to run the handler tests in both runtimes
Have updated the example app to use edge as well as node
Also support added for middleware responses (see #1150)
📎 References
Fixes #912 #1150 #1257
🎯 Testing
There is an example app running on the edge runtime on Vercel https://app-dir-auth0.vercel.app/ (source)