Skip to content

Commit db16f4e

Browse files
committed
Add authenticate HOF for server actions
1 parent 5bfbbd1 commit db16f4e

File tree

5 files changed

+66
-19
lines changed

5 files changed

+66
-19
lines changed

examples/with-next-ssr-app-directory/app/actions/checkSSRSession.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@ import { ssrConfig } from "../config/ssr";
77

88
init(ssrConfig());
99

10-
export async function checkSSRSession(e) {
11-
const cookiesStore = await cookies();
12-
// const session = await getServerComponentSession(cookiesStore);
13-
const session = await getServerActionSession(cookiesStore);
10+
export async function checkSSRSession(session) {
11+
// const session = await getServerActionSession(cookiesStore);
1412

15-
console.log(session);
13+
console.log("session", session);
1614
return Promise.resolve(true);
1715
}

examples/with-next-ssr-app-directory/app/components/ssrButton.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
"use client";
22

33
import { checkSSRSession } from "../actions/checkSSRSession";
4+
import { authenticateServerAction, init } from "supertokens-auth-react/nextjs/ssr";
45
import styles from "../page.module.css";
6+
import { ssrConfig } from "../config/ssr";
7+
8+
import { getAccessToken, attemptRefreshingSession } from "supertokens-web-js/recipe/session";
9+
10+
init(ssrConfig());
511

612
export const SSRButton = () => {
713
return (
814
<div
915
style={{ marginTop: "20px" }}
1016
onClick={async (e) => {
11-
await checkSSRSession(e);
17+
// const session = await getAccessToken();
18+
// console.log("sessionn in component", session);
19+
const result = await authenticateServerAction(checkSSRSession);
20+
console.log(result);
1221
}}
1322
className={styles.sessionButton}
1423
>

examples/with-next-ssr-app-directory/app/config/backend.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ export let backendConfig = (): TypeInput => {
1313
// debug: true,
1414
supertokens: {
1515
// this is the location of the SuperTokens core.
16-
connectionURI: "https://st-dev-bc3c6f90-79ba-11ef-ab9e-9bd286159eeb.aws.supertokens.io",
17-
apiKey: "e9zZOI7yJ0-G6gms7iGKZ17Pb-",
16+
// connectionURI: "https://st-dev-bc3c6f90-79ba-11ef-ab9e-9bd286159eeb.aws.supertokens.io",
17+
connectionURI: "http://localhost:3567",
18+
// apiKey: "e9zZOI7yJ0-G6gms7iGKZ17Pb-",
1819
},
1920
appInfo,
2021
// recipeList contains all the modules that you want to

examples/with-next-ssr-app-directory/app/config/frontend.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export function setRouter(router: ReturnType<typeof useRouter>, pathName: string
1919
export const frontendConfig = (): SuperTokensConfig => {
2020
return {
2121
appInfo,
22-
enableDebugLogs: true,
22+
// enableDebugLogs: true,
2323
recipeList: [
2424
EmailPasswordReact.init(),
2525
ThirdPartyReact.init({

lib/ts/nextjs/ssr.ts

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as jose from "jose";
22

33
import { enableLogging, logDebugMessage } from "../logger";
4+
import { doesSessionExist, getAccessTokenPayloadSecurely } from "supertokens-web-js/recipe/session";
45

56
import {
67
FRONT_TOKEN_HEADER_NAME,
@@ -110,13 +111,46 @@ export default class SuperTokensNextjsSSRAPIWrapper {
110111
* @param cookies - The cookies store exposed by next/headers (await cookies())
111112
* @returns The session context value or undefined if the session does not exist or is invalid
112113
**/
113-
static async getServerActionSession(cookies: CookiesStore): Promise<LoadedSessionContext | undefined> {
114+
static async getServerActionSession(
115+
cookies: CookiesStore
116+
): Promise<
117+
{ session: LoadedSessionContext; status: "valid" } | { status: "expired" | "invalid"; session: undefined }
118+
> {
114119
const { state, session } = await getSSRSessionState(cookies);
115120
logDebugMessage(`SSR Session State: ${state}`);
116121
if (state === "tokens-match") {
117-
return session;
122+
return { session: session as LoadedSessionContext, status: "valid" };
123+
} else if (["tokens-do-not-match", "access-token-not-found", "access-token-invalid"].includes(state)) {
124+
return { status: "expired", session: undefined };
125+
}
126+
127+
return { status: "invalid", session: undefined };
128+
}
129+
130+
// TODO: How do we check for specific st errors here
131+
// The website library just throws new Error('message')
132+
// so we don't have a reliable way to differentiate between
133+
// our errors and consumer errors
134+
static async authenticateServerAction<T extends (session?: LoadedSessionContext) => Promise<K>, K>(action: T) {
135+
try {
136+
const sessionExists = await doesSessionExist();
137+
logDebugMessage(`Session exists: ${sessionExists}`);
138+
if (!sessionExists) {
139+
logDebugMessage(`Performing action without session`);
140+
return action();
141+
}
142+
143+
const accessTokenPayload = await getAccessTokenPayloadSecurely();
144+
logDebugMessage(`Retrieved access token payload`);
145+
const loadedSessionContext = buildLoadedSessionContext(accessTokenPayload);
146+
console.log("access token payload", accessTokenPayload);
147+
148+
logDebugMessage(`Passing session context to action`);
149+
const result = await action(loadedSessionContext);
150+
return result;
151+
} catch (err) {
152+
return action();
118153
}
119-
return;
120154
}
121155

122156
/**
@@ -162,6 +196,7 @@ export const init = SuperTokensNextjsSSRAPIWrapper.init;
162196
export const getServerComponentSession = SuperTokensNextjsSSRAPIWrapper.getServerComponentSession;
163197
export const getServerActionSession = SuperTokensNextjsSSRAPIWrapper.getServerActionSession;
164198
export const getServerSidePropsSession = SuperTokensNextjsSSRAPIWrapper.getServerSidePropsSession;
199+
export const authenticateServerAction = SuperTokensNextjsSSRAPIWrapper.authenticateServerAction;
165200

166201
function getAuthPagePath(redirectPath: string): string {
167202
const authPagePath = SuperTokensNextjsSSRAPIWrapper.getConfigOrThrow().appInfo.websiteBasePath || "/auth";
@@ -207,13 +242,17 @@ async function getSSRSessionState(
207242

208243
return {
209244
state: "tokens-match",
210-
session: {
211-
userId: parsedAccessToken.payload.sub,
212-
accessTokenPayload: parsedAccessToken,
213-
doesSessionExist: true,
214-
loading: false,
215-
invalidClaims: [],
216-
},
245+
session: buildLoadedSessionContext(parsedAccessToken.payload),
246+
};
247+
}
248+
249+
function buildLoadedSessionContext(accessTokenPayload: AccessTokenPayload["up"]): LoadedSessionContext {
250+
return {
251+
userId: accessTokenPayload.sub,
252+
accessTokenPayload,
253+
doesSessionExist: true,
254+
loading: false,
255+
invalidClaims: [],
217256
};
218257
}
219258

0 commit comments

Comments
 (0)