Update with-supertokens example (#66827)
Updates the with-supertokens example to replace `getSSRSession` usage with manual JWT parsing in SSR as shown in https://github.com/supertokens/create-supertokens-app/pull/107 Co-authored-by: Sam Ko <sam@vercel.com>
This commit is contained in:
parent
642e93c6f2
commit
14b7b37a84
2 changed files with 72 additions and 29 deletions
|
@ -1,4 +1,4 @@
|
|||
import { cookies, headers } from "next/headers";
|
||||
import { cookies } from "next/headers";
|
||||
import { TryRefreshComponent } from "./tryRefreshClientComponent";
|
||||
import styles from "../page.module.css";
|
||||
import { redirect } from "next/navigation";
|
||||
|
@ -7,37 +7,72 @@ import { CelebrateIcon, SeparatorLine } from "../../assets/images";
|
|||
import { CallAPIButton } from "./callApiButton";
|
||||
import { LinksComponent } from "./linksComponent";
|
||||
import { SessionAuthForNextJS } from "./sessionAuthForNextJS";
|
||||
import { getSSRSession } from "supertokens-node/nextjs";
|
||||
import { SessionContainer } from "supertokens-node/recipe/session";
|
||||
import { ensureSuperTokensInit } from "../config/backend";
|
||||
import jwksClient from "jwks-rsa";
|
||||
import JsonWebToken from "jsonwebtoken";
|
||||
import type { JwtHeader, JwtPayload, SigningKeyCallback } from "jsonwebtoken";
|
||||
import { appInfo } from "../config/appInfo";
|
||||
|
||||
ensureSuperTokensInit();
|
||||
const client = jwksClient({
|
||||
jwksUri: `${appInfo.apiDomain}${appInfo.apiBasePath}/jwt/jwks.json`,
|
||||
});
|
||||
|
||||
function getAccessToken(): string | undefined {
|
||||
return cookies().get("sAccessToken")?.value;
|
||||
}
|
||||
|
||||
function getPublicKey(header: JwtHeader, callback: SigningKeyCallback) {
|
||||
client.getSigningKey(header.kid, (err, key) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
const signingKey = key?.getPublicKey();
|
||||
callback(null, signingKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function verifyToken(token: string): Promise<JwtPayload> {
|
||||
return new Promise((resolve, reject) => {
|
||||
JsonWebToken.verify(token, getPublicKey, {}, (err, decoded) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(decoded as JwtPayload);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function to retrieve session details on the server side.
|
||||
*
|
||||
* NOTE: This function does not use the getSSRSession function from the supertokens-node SDK
|
||||
* because getSession can update the access token. These updated tokens would not be
|
||||
* propagated to the client side, as request interceptors do not run on the server side.
|
||||
*/
|
||||
async function getSSRSessionHelper(): Promise<{
|
||||
session: SessionContainer | undefined;
|
||||
accessTokenPayload: JwtPayload | undefined;
|
||||
hasToken: boolean;
|
||||
hasInvalidClaims: boolean;
|
||||
error: Error | undefined;
|
||||
}> {
|
||||
let session: SessionContainer | undefined;
|
||||
let hasToken = false;
|
||||
let hasInvalidClaims = false;
|
||||
let error: Error | undefined = undefined;
|
||||
|
||||
const accessToken = getAccessToken();
|
||||
const hasToken = !!accessToken;
|
||||
try {
|
||||
({ session, hasToken, hasInvalidClaims } = await getSSRSession(
|
||||
cookies().getAll(),
|
||||
headers(),
|
||||
));
|
||||
} catch (err: any) {
|
||||
error = err;
|
||||
if (accessToken) {
|
||||
const decoded = await verifyToken(accessToken);
|
||||
return { accessTokenPayload: decoded, hasToken, error: undefined };
|
||||
}
|
||||
return { accessTokenPayload: undefined, hasToken, error: undefined };
|
||||
} catch (error) {
|
||||
if (error instanceof JsonWebToken.TokenExpiredError) {
|
||||
return { accessTokenPayload: undefined, hasToken, error: undefined };
|
||||
}
|
||||
return { accessTokenPayload: undefined, hasToken, error: error as Error };
|
||||
}
|
||||
return { session, hasToken, hasInvalidClaims, error };
|
||||
}
|
||||
|
||||
export async function HomePage() {
|
||||
const { session, hasToken, hasInvalidClaims, error } =
|
||||
await getSSRSessionHelper();
|
||||
const { accessTokenPayload, hasToken, error } = await getSSRSessionHelper();
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
|
@ -48,7 +83,8 @@ export async function HomePage() {
|
|||
);
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
// `accessTokenPayload` will be undefined if it the session does not exist or has expired
|
||||
if (accessTokenPayload === undefined) {
|
||||
if (!hasToken) {
|
||||
/**
|
||||
* This means that the user is not logged in. If you want to display some other UI in this
|
||||
|
@ -57,14 +93,19 @@ export async function HomePage() {
|
|||
return redirect("/auth");
|
||||
}
|
||||
|
||||
if (hasInvalidClaims) {
|
||||
return <SessionAuthForNextJS />;
|
||||
} else {
|
||||
// To learn about why the 'key' attribute is required refer to: https://github.com/supertokens/supertokens-node/issues/826#issuecomment-2092144048
|
||||
return <TryRefreshComponent key={Date.now()} />;
|
||||
}
|
||||
/**
|
||||
* This means that the session does not exist but we have session tokens for the user. In this case
|
||||
* the `TryRefreshComponent` will try to refresh the session.
|
||||
*
|
||||
* To learn about why the 'key' attribute is required refer to: https://github.com/supertokens/supertokens-node/issues/826#issuecomment-2092144048
|
||||
*/
|
||||
return <TryRefreshComponent key={Date.now()} />;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
return (
|
||||
<SessionAuthForNextJS>
|
||||
<div className={styles.homeContainer}>
|
||||
|
@ -82,7 +123,7 @@ export async function HomePage() {
|
|||
<div className={styles.innerContent}>
|
||||
<div>Your userID is:</div>
|
||||
<div className={`${styles.truncate} ${styles.userId}`}>
|
||||
{session.getUserId()}
|
||||
{accessTokenPayload.sub}
|
||||
</div>
|
||||
<CallAPIButton />
|
||||
</div>
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"jwks-rsa": "^3.1.0",
|
||||
"next": "latest",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
|
|
Loading…
Reference in a new issue