Skip to content
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

feat: Next.js SSR Improvements #877

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open

feat: Next.js SSR Improvements #877

wants to merge 21 commits into from

Conversation

bcbogdan
Copy link

@bcbogdan bcbogdan commented Feb 4, 2025

Summary of change

The following PR improves SSR support in Next.js applications.
To get the session data in the context of server side rendering you now have to call getSSRSession either inside a Server Component, or in getServerSideProps (for older next applications).
There's two parts to this change:

Fetching the session during SSR

The logic for this is kept in the ssr.ts file. The function reads the active tokens and has 3 possible outcomes:

  • redirect to the login page if there was an issue (unable to read the tokens, token payload was invalid)
  • redirect to the refresh api if the tokens have expired or if the front token payload is different from the access token payload
  • return the session value

Refreshing a session

The getSSRSession function redirects to a refresh endpoint that we do not expose from our existing API.
Hence, this change adds logic inside the next.js middleware to handle such a request. The custom middleware calls the backend API using a fetch and updates the response with the new tokens.

This part is now kept mostly in the middleware.ts file, in the react library. It's there just to make it easy to review during this stage. It should be moved in the node SDK package as a separate, Nextjs specific API.

Test Plan

(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work. Bonus points for screenshots and videos!)

Documentation changes

(If relevant, please create a PR in our docs repo, or create a checklist here highlighting the necessary changes)

Checklist for important updates

  • Changelog has been updated
  • frontendDriverInterfaceSupported.json file has been updated (if needed)
  • Changes to the version if needed
    • In package.json
    • In package-lock.json
    • In lib/ts/version.ts
  • Had run npm run build-pretty
  • Had installed and ran the pre-commit hook
  • Issue this PR against the latest non released version branch.
    • To know which one it is, run find the latest released tag (git tag) in the format vX.Y.Z, and then find the latest branch (git branch --all) whose X.Y is greater than the latest released tag.
    • If no such branch exists, then create one from the latest released branch.
  • If added a new recipe interface, then make sure that the implementation of it uses NON arrow functions only (like someFunc: function () {..}).
  • If I added a new recipe, I also added the recipe entry point into the size-limit section of package.json with the size limit set to the current size rounded up.
  • If I added a new recipe, I also added the recipe entry point into the rollup.config.mjs
  • If I added a new login method, I modified the list in lib/ts/types.ts
  • If I added a factor id, I modified the list in lib/ts/recipe/multifactorauth/types.ts

Remaining TODOs for this PR

  • Item1
  • Item2

Copy link

github-actions bot commented Feb 12, 2025

size-limit report 📦

Path Size
lib/build/index.js 24.58 KB (+0.25% 🔺)
recipe/session/index.js 25.29 KB (+0.31% 🔺)
recipe/session/prebuiltui.js 30 KB (+0.2% 🔺)
recipe/thirdparty/index.js 32.37 KB (+0.19% 🔺)
recipe/emailpassword/index.js 11.74 KB (+0.68% 🔺)
recipe/emailverification/index.js 8.03 KB (+0.67% 🔺)
recipe/passwordless/index.js 15.64 KB (+0.55% 🔺)
recipe/emailverification/prebuiltui.js 34.73 KB (+0.37% 🔺)
recipe/thirdparty/prebuiltui.js 53.91 KB (+0.25% 🔺)
recipe/emailpassword/prebuiltui.js 40.91 KB (+0.3% 🔺)
recipe/passwordless/prebuiltui.js 128.89 KB (+0.12% 🔺)
recipe/multitenancy/index.js 6.9 KB (+1.05% 🔺)
recipe/multifactorauth/index.js 11.68 KB (+0.72% 🔺)
recipe/multifactorauth/prebuiltui.js 33.7 KB (+0.36% 🔺)
recipe/oauth2provider/index.js 7.65 KB (+0.74% 🔺)
recipe/oauth2provider/prebuiltui.js 32.07 KB (+0.29% 🔺)

@bcbogdan bcbogdan changed the title Next.js SSR Improvements feat: Next.js SSR Improvements Feb 24, 2025
Comment on lines +80 to +81
// TODO: This method is isolated atm to make it easier to test and debug
// In the end it should be merged in the getSSRSession function
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to merge it into getSSRSession? At first glance this works well as both the usecase and the impl is different enough.

const redirectTo = "/";

const authPagePath = `${getAuthPagePath()}`;
const refreshLocation = `/api/auth/session/refresh?redirectTo=${redirectTo}`;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getRefreshLocation?

Comment on lines +189 to +190
// TODO:
// - Do we need to check the token version and handle ERR_JWKS_MULTIPLE_MATCHING_KEYS like in the node SDK?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we do, that only comes up with really old tokens IIRC

): Promise<{ isValid: true; payload: AccessTokenPayload["up"] } | { isValid: false }> {
const appInfo = SuperTokensNextjsSSRAPIWrapper.getConfigOrThrow().appInfo;
const jwksUrl = new URL(`${appInfo.apiBasePath}/jwt/jwks.json`, appInfo.apiDomain);
const JWKS = jose.createRemoteJWKSet(jwksUrl);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be cached/moved into a global location.

logDebugMessage("Missing tokens from refresh response");
return redirectToAuthPage(request);
}
const redirectUrl = new URL(redirectTo, request.url);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we check if redirectTo is relative?

Comment on lines +169 to +171
// getSetCookie was added in node 18 and our build target is ES5
// This should not a problem here since the function runs in the Vercel edge runtime environment
// @ts-expect-error TS(2339): Property 'getSetCookie' does not exist on type 'Headers'.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a TODO: let's review if we could raise our build-target

@@ -113,3 +113,5 @@ export function validateAndCompareOnFailureRedirectionURLToCurrent(redirectURL:

return currentUrl === fullRedirectURL;
}

export function getSessionForSSR() {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be removed.

@@ -75,3 +75,28 @@ export type AccessDeniedThemeProps = {
export type ComponentOverrideMap = {
SessionAccessDenied_Override?: ComponentOverride<typeof AccessDeniedScreenTheme>;
};

export type AccessTokenPayload = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be removed.

@@ -0,0 +1,154 @@
import * as jose from "jose";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be removed.

@@ -0,0 +1,205 @@
import * as jose from "jose";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to remove this or move it into the with-typescript folder (that's what we use to "test" typings in ci/scripts as well as a scratch file)

Comment on lines +22 to +23
* SessionAuthForNextJS will handle proper redirection for the user based on the different session states.
* It will redirect to the login page if the session does not exist etc.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say it handles redirections on the client side.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants