Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ test-output
.cursor/rules/nx-rules.mdc
.github/instructions/nx.instructions.md

# SvelteKit
.svelte-kit

# Gemini local knowledge base files
GEMINI.md
**/GEMINI.md
12 changes: 6 additions & 6 deletions e2e/oidc-app/src/utils/oidc-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,14 @@ export async function oidcApp({ config, urlParams }) {
});

document.getElementById('login-redirect').addEventListener('click', async () => {
const authorizeUrl = await oidcClient.authorize.url();
if (typeof authorizeUrl !== 'string' && 'error' in authorizeUrl) {
console.error('Authorization URL Error:', authorizeUrl);
displayError(authorizeUrl);
const authorizeResult = await oidcClient.authorize.url();
if ('error' in authorizeResult) {
console.error('Authorization URL Error:', authorizeResult);
displayError(authorizeResult);
return;
} else {
console.log('Authorization URL:', authorizeUrl);
window.location.assign(authorizeUrl);
console.log('Authorization URL:', authorizeResult.url);
window.location.assign(authorizeResult.url);
}
});

Expand Down
26 changes: 26 additions & 0 deletions e2e/svelte-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "@forgerock/svelte-app",
"version": "1.0.0",
"private": true,
"description": "SvelteKit SSR proof of concept for Journey Client",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@forgerock/journey-client": "workspace:*",
"@forgerock/oidc-client": "workspace:*"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^6.0.0",
"@sveltejs/kit": "^2.21.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"svelte": "^5.0.0",
"vite": "catalog:vite"
},
"nx": {
"tags": ["scope:e2e"]
}
}
12 changes: 12 additions & 0 deletions e2e/svelte-app/src/app.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Journey Client SSR PoC</title>
%sveltekit.head%
</head>
<body>
<div id="app">%sveltekit.body%</div>
</body>
</html>
17 changes: 17 additions & 0 deletions e2e/svelte-app/src/lib/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Server configuration for the SSR proof of concept.
* Points at the AM mock API running on localhost:9443.
*/
export const WELLKNOWN_URL =
'http://localhost:9443/am/oauth2/realms/root/.well-known/openid-configuration';

export const CLIENT_ID = 'SvelteSSRClient';
export const REDIRECT_URI = 'http://localhost:5174/callback';
export const SCOPE = 'openid profile';

/** No-op storage adapter for server-side usage where browser storage is unavailable. */
export const noopStorage = {
get: async () => null,
set: async () => {},
remove: async () => {},
};
15 changes: 15 additions & 0 deletions e2e/svelte-app/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script>
let { children } = $props();
</script>

<main>
{@render children()}
</main>

<style>
main {
max-width: 480px;
margin: 2rem auto;
font-family: system-ui, sans-serif;
}
</style>
92 changes: 92 additions & 0 deletions e2e/svelte-app/src/routes/+page.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { redirect } from '@sveltejs/kit';
import { journey } from '@forgerock/journey-client';
import { oidc } from '@forgerock/oidc-client';
import { WELLKNOWN_URL, CLIENT_ID, REDIRECT_URI, SCOPE, noopStorage } from '$lib/config.js';
import type { PageServerLoad, Actions } from './$types';

/**
* Server-side load function.
*
* Initializes the journey client with noop storage (no sessionStorage on server)
* and calls start() to fetch the first authentication step. The raw step payload
* is serialized and passed to the client for SSR rendering.
*/
export const load: PageServerLoad = async () => {
try {
const client = await journey({
config: {
serverConfig: { wellknown: WELLKNOWN_URL },
storage: { type: 'custom', name: 'journey-step', custom: noopStorage },
},
});

const result = await client.start();

if ('payload' in result) {
return {
stepPayload: result.payload,
error: null,
};
}

return {
stepPayload: null,
error: 'error' in result ? result : { error: 'unexpected', message: 'Unexpected result' },
};
} catch (e) {
return {
stepPayload: null,
error: {
error: 'server_init_failed',
message: e instanceof Error ? e.message : 'Failed to initialize journey client on server',
},
};
}
};

/**
* Form actions — the authorize action generates a PKCE authorize URL on the server,
* stores the verifier in a cookie, and redirects the browser to the authorize endpoint.
*/
export const actions: Actions = {
authorize: async ({ cookies }) => {
const client = await oidc({
config: {
serverConfig: { wellknown: WELLKNOWN_URL },
clientId: CLIENT_ID,
redirectUri: REDIRECT_URI,
scope: SCOPE,
responseType: 'code',
},
storage: { type: 'custom', name: CLIENT_ID, custom: noopStorage },
});

if (!client || 'error' in client) {
return { error: 'Failed to initialize OIDC client' };
}

// Generate authorize URL with PKCE — returns { url, verifier, state }
const result = await client.authorize.url();

if ('error' in result) {
return { error: result.error };
}

// Store PKCE verifier + state in an httpOnly cookie for the callback route
cookies.set('pkce_verifier', result.verifier, {
path: '/',
httpOnly: true,
sameSite: 'lax',
maxAge: 300, // 5 minutes
});
cookies.set('pkce_state', result.state, {
path: '/',
httpOnly: true,
sameSite: 'lax',
maxAge: 300,
});

// Redirect browser to authorization endpoint
redirect(303, result.url);
},
};
Loading
Loading