Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ea7be03
feat(journey-client): install and configure journey-client
vatsalparikh Apr 2, 2026
99b0cd7
refactor(journey): migrate journey logic from legacy forgerock sdk to…
vatsalparikh Apr 14, 2026
701fcc2
refactor(journey): adding support for query params to resume function…
vatsalparikh Apr 15, 2026
8bacddc
refactor(journey): GenericError handling support
vatsalparikh Apr 15, 2026
362583c
refactor(journey): refactor stories, mocks, and story.svelte filees f…
vatsalparikh Apr 15, 2026
04034bd
refactor(journey): refactor stages to use journey client
vatsalparikh Apr 16, 2026
9a6bcbc
chore: add journey-client migration changeset
vatsalparikh Apr 16, 2026
6544844
refactor(journey): configure login-app and login-widget to use the jo…
vatsalparikh Apr 17, 2026
ae0366e
refactor(journey): update demo custom components to use journey clien…
vatsalparikh Apr 17, 2026
be6f012
refactor(journey): update copyright headers
vatsalparikh Apr 17, 2026
d63fffe
refactor(journey): add FR_AM_WELLKNOWN_URL to ci and update lock file
vatsalparikh Apr 17, 2026
da4d4b3
refactor(journey): fixing failing ci tests
vatsalparikh Apr 17, 2026
e1c623a
test(journey): more changes to fix ci tests
vatsalparikh Apr 17, 2026
e71321c
refactor(journey): updated journey client logic so now journey store …
vatsalparikh Apr 20, 2026
40f9a7e
fix(journey): adding redirect method to journey store and fixing some…
vatsalparikh Apr 28, 2026
54452a1
fix(journey): use JourneyResult and JourneyLoginFailure types instead…
vatsalparikh Apr 29, 2026
06e74ee
fix(journey): use Device from journey-client instead of FRDevice
vatsalparikh Apr 30, 2026
1226501
fix(journey): moved journey clint configuration into journey store
vatsalparikh May 2, 2026
c0319c4
fix(journey): fix failing CI tests
vatsalparikh May 2, 2026
af99389
chore(login-app): show helpful error message when env variables are n…
vatsalparikh May 5, 2026
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
5 changes: 5 additions & 0 deletions .changeset/journey-client-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@forgerock/login-widget': major
---

Migrate the journey flow implementation from `@forgerock/javascript-sdk` to `@forgerock/journey-client`.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ FR_AM_COOKIE_NAME=<<<(string) The name of the ForgeRock server cookie>>>
FR_REALM_PATH=<<<(string) Realm path of AM>>>
FR_OAUTH_PUBLIC_CLIENT=<<<(string) Your Public OAuth client name/ID>>>
# FR_OAUTH_SCOPE=openid profile me.read # optional — AM uses client defaults if omitted
FR_AM_WELLKNOWN_URL=<<<(string) Your OIDC Discovery Wellknown Endpoint>>>

# BFF session signing
COOKIE_SECRET=<<<(string) At least 32 characters for HMAC signing>>>
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
FR_AM_COOKIE_NAME: ${{ secrets.VITE_FR_AM_COOKIE_NAME }}
FR_OAUTH_PUBLIC_CLIENT: ${{ secrets.VITE_FR_OAUTH_PUBLIC_CLIENT }}
FR_REALM_PATH: ${{ secrets.VITE_FR_REALM_PATH }}
FR_AM_WELLKNOWN_URL: ${{ secrets.FR_AM_WELLKNOWN_URL }}
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
Expand Down Expand Up @@ -122,6 +123,7 @@ jobs:
FR_AM_COOKIE_NAME: ${{ secrets.VITE_FR_AM_COOKIE_NAME }}
FR_OAUTH_PUBLIC_CLIENT: ${{ secrets.VITE_FR_OAUTH_PUBLIC_CLIENT }}
FR_REALM_PATH: ${{ secrets.VITE_FR_REALM_PATH }}
FR_AM_WELLKNOWN_URL: ${{ secrets.FR_AM_WELLKNOWN_URL }}
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ The ForgeRock Login Widget is an all-inclusive UI component for adding authentic

It works with React, Vue, Angular, Svelte, and vanilla JavaScript — any modern client-side JavaScript framework. It does not currently support server-side rendering (SSR) or Node.js.

The widget uses the [ForgeRock SDK for JavaScript](https://docs.pingidentity.com/sdks/latest/sdks/tutorials/javascript/index.html) internally and adds a UI rendering layer with state management. This rendering layer is built with [Svelte](https://svelte.dev/) and [Tailwind](https://tailwindcss.com/), but both are compiled away — the resulting widget is framework-agnostic with no runtime dependencies.
The widget uses [Journey Client](https://developer.pingidentity.com/orchsdks/journey/usage/javascript/index.html) for journey execution, and the [ForgeRock SDK for JavaScript](https://docs.pingidentity.com/sdks/latest/sdks/tutorials/javascript/index.html) for OAuth/OIDC tokens, user info, and request utilities. It adds a UI rendering layer with state management. This rendering layer is built with [Svelte](https://svelte.dev/) and [Tailwind](https://tailwindcss.com/), but both are compiled away — the resulting widget is framework-agnostic with no runtime dependencies.

## Quick Start

Expand All @@ -52,7 +52,14 @@ import Widget, { configuration, journey } from '@forgerock/login-widget';

const config = configuration();
config.set({
// REQUIRED for journeys
journeyClient: {
serverConfig: {
wellknown: 'https://your-tenant.forgeblocks.com/am/.well-known/am-configuration',
},
},
forgerock: {
// REQUIRED if you use OAuth/OIDC tokens, user info, or request()
serverConfig: {
baseUrl: 'https://your-tenant.forgeblocks.com/am/',
timeout: 3000,
Expand Down
1 change: 1 addition & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ E2E tests require ForgeRock AM connection details. Set these in your environment
| `VITE_FR_AM_COOKIE_NAME` | AM session cookie name |
| `VITE_FR_OAUTH_PUBLIC_CLIENT` | OAuth 2.0 client ID |
| `VITE_FR_REALM_PATH` | AM realm path |
| `FR_AM_WELLKNOWN_URL` | AM wellknown URL |

## Linting

Expand Down
13 changes: 7 additions & 6 deletions apps/login-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,13 @@ pnpm --filter @forgerock/login-app run preview

The app requires environment variables to connect to a ForgeRock AM instance. Set these in a `.env` file at the repository root or export them in your shell.

| Variable | Required | Description |
| ----------------------------- | -------- | ---------------------------------------------------------------------- |
| `VITE_FR_AM_URL` | Yes | ForgeRock AM base URL (e.g., `https://openam-sdks.forgeblocks.com/am`) |
| `VITE_FR_AM_COOKIE_NAME` | Yes | AM session cookie name |
| `VITE_FR_OAUTH_PUBLIC_CLIENT` | No | OAuth 2.0 public client ID |
| `VITE_FR_REALM_PATH` | No | AM realm path |
| Variable | Required | Description |
| ----------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------- |
| `VITE_FR_AM_URL` | Yes | ForgeRock AM base URL (e.g., `https://openam-sdks.forgeblocks.com/am`) |
| `VITE_FR_AM_COOKIE_NAME` | Yes | AM session cookie name |
| `VITE_FR_OAUTH_PUBLIC_CLIENT` | No | OAuth 2.0 public client ID |
| `VITE_FR_REALM_PATH` | No | AM realm path |
| `FR_AM_WELLKNOWN_URL` | Yes | ForgeRock AM Wellknown URL (e.g., `https://openam-sdks.forgeblocks.com/am/oauth2/alpha/.well-known/openid-configuration`) |

## Type Checking

Expand Down
31 changes: 24 additions & 7 deletions apps/login-app/src/routes/(app)/+layout.server.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
/**
*
* Copyright © 2025 Ping Identity Corporation. All right reserved.
* Copyright © 2025-2026 Ping Identity Corporation. All right reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*
**/

import { env } from '$env/dynamic/private';
import { error } from '@sveltejs/kit';
import type { LayoutServerLoad } from './$types';

export const load: LayoutServerLoad = () => ({
amUrl: env.FR_AM_URL ?? '',
clientId: env.FR_OAUTH_PUBLIC_CLIENT ?? '',
realmPath: env.FR_REALM_PATH ?? '',
scope: env.FR_OAUTH_SCOPE,
});
export const load: LayoutServerLoad = () => {
const amUrl = env.FR_AM_URL;
const clientId = env.FR_OAUTH_PUBLIC_CLIENT;
const realmPath = env.FR_REALM_PATH;
const wellknown = env.FR_AM_WELLKNOWN_URL;
const scope = env.FR_OAUTH_SCOPE;

if (!amUrl || !realmPath || !wellknown) {
throw error(
500,
'Login App is not configured. Ensure the required environment variables like FR_AM_URL, FR_REALM_PATH, and FR_AM_WELLKNOWN_URL are set before starting the app.',
);
}

return {
amUrl,
clientId,
realmPath,
scope,
wellknown,
};
};
2 changes: 1 addition & 1 deletion apps/login-app/src/routes/(app)/+layout.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
*
* Copyright © 2025 Ping Identity Corporation. All right reserved.
* Copyright © 2025-2026 Ping Identity Corporation. All right reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
Expand Down
9 changes: 6 additions & 3 deletions apps/login-app/src/routes/(app)/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@
const journeyParam = $page.url.searchParams.get('journey');
const suspendedIdParam = $page.url.searchParams.get('suspendedId');

const journeyStore: JourneyStore = initializeJourney();
const journeyStore: JourneyStore = initializeJourney({
serverConfig: {
wellknown: data.wellknown,
Comment thread
vatsalparikh marked this conversation as resolved.
},
});

let hasSubmitted = false;
let redirectForm: HTMLFormElement | null = null;
Expand All @@ -47,7 +51,6 @@
await journeyStore.resume(location.href);
goto('/', { replaceState: true });
} else {

// noSession set to false is required to receive session token from AM
const query: Record<string, string> = {
noSession: 'false',
Expand All @@ -62,7 +65,7 @@
query.gotoOnFail = data.redirectParams?.gotoOnFail;

journeyStore.start({
tree: journeyParam || authIndexValue || undefined,
journey: journeyParam || authIndexValue || 'Login',
query,
// recaptchaAction: 'MyTestAction',
});
Expand Down
10 changes: 7 additions & 3 deletions apps/login-app/src/routes/(app)/register/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!--

Copyright © 2025 Ping Identity Corporation. All right reserved.
Copyright © 2025-2026 Ping Identity Corporation. All right reserved.

This software may be modified and distributed under the terms
of the MIT license. See the LICENSE file for details.
Expand All @@ -23,7 +23,11 @@
/** @type {import('./$types').PageData} */
export let data;

const journeyStore: JourneyStore = initializeJourney();
const journeyStore: JourneyStore = initializeJourney({
serverConfig: {
wellknown: data.wellknown,
},
});
const oauthStore: OAuthStore = initializeOAuth();
const userStore: UserStore = initializeUser();

Expand All @@ -34,7 +38,7 @@

// Use if not initializing journey in a "context module"
onMount(async () => {
journeyStore.start({ tree: 'Registration' });
journeyStore.start({ journey: 'Registration' });
});

$: {
Expand Down
7 changes: 6 additions & 1 deletion apps/login-app/src/routes/e2e/widget/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@
}

onMount(async () => {
configuration().set({
configuration({
journeyClient: {
serverConfig: {
wellknown: 'https://openam-sdks.forgeblocks.com/am/oauth2/alpha/.well-known/openid-configuration',
},
},
forgerock: {
clientId: 'WebOAuthClient',
redirectUri: `${window.location.origin}/callback`,
Expand Down
50 changes: 28 additions & 22 deletions apps/login-app/src/routes/e2e/widget/inline/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!--

Copyright © 2025 Ping Identity Corporation. All right reserved.
Copyright © 2025-2026 Ping Identity Corporation. All right reserved.

This software may be modified and distributed under the terms
of the MIT license. See the LICENSE file for details.
Expand All @@ -20,10 +20,6 @@
email: string;
};

const config = configuration();
const componentEvents = component();
const journeyEvents = journey();

let authIndexValueParam = $page.url.searchParams.get('authIndexValue');
let journeyParam = $page.url.searchParams.get('journey');
let recaptchaParam = $page.url.searchParams.get('recaptchaAction');
Expand All @@ -38,21 +34,6 @@
userResponse = null;
}

componentEvents.subscribe((event) => {
if (event.lastAction === 'mount') {
console.log('Form mounted');
}
});
journeyEvents.subscribe((event) => {
if (event?.user?.successful) {
userEvent = event.user;
userResponse = event.user.response as UserResponseObj;
}
if (event.journey.error || event.oauth.error || event.user.error) {
console.log('Login failure event fired');
}
});

onMount(async () => {
let content;
/**
Expand All @@ -63,7 +44,12 @@
content = response.ok && (await response.json());
}

config.set({
configuration({
journeyClient: {
serverConfig: {
wellknown: 'https://openam-sdks.forgeblocks.com/am/oauth2/alpha/.well-known/openid-configuration',
},
},
forgerock: {
clientId: 'WebOAuthClient',
redirectUri: `${window.location.origin}/callback`,
Expand All @@ -78,6 +64,26 @@
termsAndConditions: 'https://www.forgerock.com/terms',
},
});

const componentEvents = component();
const journeyEvents = journey();

componentEvents.subscribe((event) => {
if (event.lastAction === 'mount') {
console.log('Form mounted');
}
});

journeyEvents.subscribe((event) => {
if (event?.user?.successful) {
userEvent = event.user;
userResponse = event.user.response as UserResponseObj;
}
if (event.journey.error || event.oauth.error || event.user.error) {
console.log('Login failure event fired');
}
});

new Widget({ target: formEl, props: { type: 'inline' } });
// Start the journey after initialization or within the form.onMount event
journeyEvents.start({
Expand All @@ -97,4 +103,4 @@
</ul>
<button on:click={logout}>Logout</button>
{/if}
<div bind:this={formEl} class={`${userEvent?.successful ? 'tw_hidden' : ''} tw_p-6`} />
<div bind:this={formEl} class={`${userEvent?.successful ? 'tw_hidden' : ''} tw_p-6`}></div>
Loading
Loading