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

Update with-supertokens example #66827

Merged
merged 2 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Update with-supertokens example
  • Loading branch information
anku255 committed Jun 13, 2024
commit 196fb031b213c42de57e9146989376f51b295f55
99 changes: 70 additions & 29 deletions examples/with-supertokens/app/components/home.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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 (
Expand All @@ -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
Expand All @@ -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}>
Expand All @@ -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>
Expand Down
2 changes: 2 additions & 0 deletions examples/with-supertokens/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"lint": "next lint"
},
"dependencies": {
"jsonwebtoken": "^9.0.2",
"jwks-rsa": "^3.1.0",
"next": "latest",
"react": "^18",
"react-dom": "^18",
Expand Down
Loading