Skip to content
Open
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
6 changes: 6 additions & 0 deletions .changeset/brave-foxes-dance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@forgerock/iframe-manager': minor
'@forgerock/oidc-client': minor
---

Add `user.session()` method to oidc client for OIDC prompt=none session verification, with `response_type=none` and `response_type=id_token` support.
3 changes: 3 additions & 0 deletions e2e/oidc-app/src/ping-am/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ <h1>OIDC App | PingAM Login</h1>
<button id="logout">Logout</button>
<button id="user-info-btn">User Info</button>
<button id="revoke">Revoke Token</button>
<button id="session-check-btn">Session Check (none, iframe)</button>
<button id="session-check-no-redirect-btn">Session Check (none, no redirect)</button>
<button id="session-check-id-token-btn">Session Check (id_token)</button>
<a href="/ping-am/">Start Over</a>
</div>
<script type="module" src="./main.ts"></script>
Expand Down
1 change: 1 addition & 0 deletions e2e/oidc-app/src/ping-one/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ <h1>OIDC App | P1 Login</h1>
<button id="logout">Logout</button>
<button id="user-info-btn">User Info</button>
<button id="revoke">Revoke Token</button>
<button id="session-check-id-token-btn">Session Check (id_token)</button>
<a href="/ping-one/">Start Over</a>
</div>
<script type="module" src="./main.ts"></script>
Expand Down
213 changes: 168 additions & 45 deletions e2e/oidc-app/src/utils/oidc-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,42 +10,76 @@ import { oidc } from '@forgerock/oidc-client';
import type {
AuthorizationError,
GenericError,
GetAuthorizationUrlOptions,
OauthTokens,
OidcClient,
OidcConfig,
SessionCheckOptions,
TokenExchangeErrorResponse,
} from '@forgerock/oidc-client/types';

let tokenIndex = 0;

function displayError(error) {
function displayError(error: unknown) {
const errorEl = document.createElement('div');
errorEl.innerHTML = `<p><strong>Error:</strong> <span class="error">${JSON.stringify(error, null, 2)}</span></p>`;
const p = document.createElement('p');
const strong = document.createElement('strong');
strong.textContent = 'Error:';
const span = document.createElement('span');
span.className = 'error';
span.textContent = JSON.stringify(error, null, 2);
p.append(strong, document.createTextNode(' '), span);
errorEl.appendChild(p);
document.body.appendChild(errorEl);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

function displayTokenResponse(
response: OauthTokens | TokenExchangeErrorResponse | GenericError | AuthorizationError,
) {
const appEl = document.getElementById('app');
if ('error' in response || !('accessToken' in response)) {
console.error('Token Error:', response);
displayError(response);
} else {
console.log('Token Response:', response);
document.getElementById('logout').style.display = 'block';
document.getElementById('user-info-btn').style.display = 'block';
document.getElementById('login-background').style.display = 'none';
document.getElementById('login-redirect').style.display = 'none';
const appEl = document.getElementById('app');
const logoutEl = document.getElementById('logout');
const userInfoBtnEl = document.getElementById('user-info-btn');
const loginBackgroundEl = document.getElementById('login-background');
const loginRedirectEl = document.getElementById('login-redirect');

if (logoutEl) {
logoutEl.style.display = 'block';
}
if (userInfoBtnEl) {
userInfoBtnEl.style.display = 'block';
}
if (loginBackgroundEl) {
loginBackgroundEl.style.display = 'none';
}
if (loginRedirectEl) {
loginRedirectEl.style.display = 'none';
}

const tokenInfoEl = document.createElement('div');
tokenInfoEl.innerHTML = `<p><strong>Access Token:</strong> <span id="accessToken-${tokenIndex}">${response.accessToken}</span></p>`;
appEl.appendChild(tokenInfoEl);
const tokenP = document.createElement('p');
const tokenStrong = document.createElement('strong');
tokenStrong.textContent = 'Access Token:';
const tokenSpan = document.createElement('span');
tokenSpan.id = `accessToken-${tokenIndex}`;
tokenSpan.textContent = response.accessToken;
tokenP.append(tokenStrong, document.createTextNode(' '), tokenSpan);
tokenInfoEl.appendChild(tokenP);
appEl?.appendChild(tokenInfoEl);
tokenIndex++;
}
}

export async function oidcApp({ config, urlParams }) {
export async function oidcApp({
config,
urlParams,
}: {
config: OidcConfig;
urlParams: URLSearchParams;
}) {
const code = urlParams.get('code');
const state = urlParams.get('state');
const piflow = urlParams.get('piflow');
Expand All @@ -56,20 +90,23 @@ export async function oidcApp({ config, urlParams }) {
});
if ('error' in oidcClient) {
displayError(oidcClient);
return;
}

document.getElementById('login-background').addEventListener('click', async () => {
const authorizeOptions: GetAuthorizationUrlOptions =
document.getElementById('login-background')?.addEventListener('click', async () => {
const authorizeOptions =
piflow === 'true'
? {
clientId: config.clientId,
redirectUri: config.redirectUri,
scope: config.scope,
responseType: config.responseType ?? 'code',
responseMode: 'pi.flow',
responseMode: 'pi.flow' as const,
}
: undefined;
const response = await oidcClient.authorize.background(authorizeOptions);
const response = await oidcClient.authorize?.background(authorizeOptions);

if (!response) return;

if ('error' in response) {
console.error('Authorization Error:', response);
Expand All @@ -85,13 +122,16 @@ export async function oidcApp({ config, urlParams }) {
// Handle success response from background authorization
} else if ('code' in response) {
console.log('Authorization Code:', response.code);
const tokenResponse = await oidcClient.token.exchange(response.code, response.state);
displayTokenResponse(tokenResponse);
const tokenResponse = await oidcClient.token?.exchange(response.code, response.state);
if (tokenResponse) {
displayTokenResponse(tokenResponse);
}
}
});

document.getElementById('login-redirect').addEventListener('click', async () => {
const authorizeUrl = await oidcClient.authorize.url();
document.getElementById('login-redirect')?.addEventListener('click', async () => {
const authorizeUrl = await oidcClient.authorize?.url();
if (!authorizeUrl) return;
if (typeof authorizeUrl !== 'string' && 'error' in authorizeUrl) {
console.error('Authorization URL Error:', authorizeUrl);
displayError(authorizeUrl);
Expand All @@ -102,23 +142,31 @@ export async function oidcApp({ config, urlParams }) {
}
});

document.getElementById('get-tokens').addEventListener('click', async () => {
const response = await oidcClient.token.get();
displayTokenResponse(response);
document.getElementById('get-tokens')?.addEventListener('click', async () => {
const response = await oidcClient.token?.get();
if (response) {
displayTokenResponse(response);
}
});

document.getElementById('get-tokens-background').addEventListener('click', async () => {
const response = await oidcClient.token.get({ backgroundRenew: true });
displayTokenResponse(response);
document.getElementById('get-tokens-background')?.addEventListener('click', async () => {
const response = await oidcClient.token?.get({ backgroundRenew: true });
if (response) {
displayTokenResponse(response);
}
});

document.getElementById('renew-tokens').addEventListener('click', async () => {
const response = await oidcClient.token.get({ backgroundRenew: true, forceRenew: true });
displayTokenResponse(response);
document.getElementById('renew-tokens')?.addEventListener('click', async () => {
const response = await oidcClient.token?.get({ backgroundRenew: true, forceRenew: true });
if (response) {
displayTokenResponse(response);
}
});

document.getElementById('user-info-btn').addEventListener('click', async () => {
const userInfo = await oidcClient.user.info();
document.getElementById('user-info-btn')?.addEventListener('click', async () => {
const userInfo = await oidcClient.user?.info();

if (!userInfo) return;

if ('error' in userInfo) {
console.error('User Info Error:', userInfo);
Expand All @@ -128,43 +176,118 @@ export async function oidcApp({ config, urlParams }) {

const appEl = document.getElementById('app');
const userInfoEl = document.createElement('div');
userInfoEl.innerHTML = `<p><strong>User Info:</strong> <span id="userInfo">${JSON.stringify(userInfo, null, 2)}</span></p>`;
appEl.appendChild(userInfoEl);
const userInfoP = document.createElement('p');
const userInfoStrong = document.createElement('strong');
userInfoStrong.textContent = 'User Info:';
const userInfoSpan = document.createElement('span');
userInfoSpan.id = 'userInfo';
userInfoSpan.textContent = JSON.stringify(userInfo, null, 2);
userInfoP.append(userInfoStrong, document.createTextNode(' '), userInfoSpan);
userInfoEl.appendChild(userInfoP);
appEl?.appendChild(userInfoEl);
}
});

document.getElementById('revoke').addEventListener('click', async () => {
const response = await oidcClient.token.revoke();
document.getElementById('revoke')?.addEventListener('click', async () => {
const response = await oidcClient.token?.revoke();

if (!response) return;

if ('error' in response) {
console.error('Token Revocation Error:', response);
displayError(response);
} else {
const appEl = document.getElementById('app');
const userInfoEl = document.createElement('div');
userInfoEl.innerHTML = `<p>Token successfully revoked</p>`;
appEl.appendChild(userInfoEl);
const revokeEl = document.createElement('div');
const revokeP = document.createElement('p');
revokeP.textContent = 'Token successfully revoked';
revokeEl.appendChild(revokeP);
appEl?.appendChild(revokeEl);
}
});

document.getElementById('logout').addEventListener('click', async () => {
const response = await oidcClient.user.logout();
document.getElementById('logout')?.addEventListener('click', async () => {
const response = await oidcClient.user?.logout();

if (!response) return;

if ('error' in response) {
console.error('Logout Error:', response);
displayError(response);
} else {
console.log('Logout successful');
document.getElementById('logout').style.display = 'none';
document.getElementById('user-info-btn').style.display = 'none';
document.getElementById('login-background').style.display = 'block';
document.getElementById('login-redirect').style.display = 'block';
const logoutEl = document.getElementById('logout');
const userInfoBtnEl = document.getElementById('user-info-btn');
const loginBackgroundEl = document.getElementById('login-background');
const loginRedirectEl = document.getElementById('login-redirect');

if (logoutEl) {
logoutEl.style.display = 'none';
}
if (userInfoBtnEl) {
userInfoBtnEl.style.display = 'none';
}
if (loginBackgroundEl) {
loginBackgroundEl.style.display = 'block';
}
if (loginRedirectEl) {
loginRedirectEl.style.display = 'block';
}
window.location.assign(window.location.origin + window.location.pathname);
}
});

document.getElementById('session-check-btn')?.addEventListener('click', async () => {
const result = await oidcClient.user?.session();
const appEl = document.getElementById('app');
const el = document.createElement('div');
const title = document.createElement('p');
const titleStrong = document.createElement('strong');
titleStrong.textContent = 'Session Check (none, iframe):';
title.appendChild(titleStrong);
const pre = document.createElement('pre');
pre.id = 'session-check-result';
pre.textContent = JSON.stringify(result, null, 2);
el.append(title, pre);
appEl?.appendChild(el);
});

document.getElementById('session-check-no-redirect-btn')?.addEventListener('click', async () => {
const options: SessionCheckOptions = { redirectUri: '' };
const result = await oidcClient.user?.session(options);
const appEl = document.getElementById('app');
const el = document.createElement('div');
const title = document.createElement('p');
const titleStrong = document.createElement('strong');
titleStrong.textContent = 'Session Check (none, no redirect):';
title.appendChild(titleStrong);
const pre = document.createElement('pre');
pre.id = 'session-check-no-redirect-result';
pre.textContent = JSON.stringify(result, null, 2);
el.append(title, pre);
appEl?.appendChild(el);
});

document.getElementById('session-check-id-token-btn')?.addEventListener('click', async () => {
const options: SessionCheckOptions = { responseType: 'id_token' };
const result = await oidcClient.user?.session(options);
const appEl = document.getElementById('app');
const el = document.createElement('div');
const title = document.createElement('p');
const titleStrong = document.createElement('strong');
titleStrong.textContent = 'Session Check (id_token):';
title.appendChild(titleStrong);
const pre = document.createElement('pre');
pre.id = 'session-check-id-token-result';
pre.textContent = JSON.stringify(result, null, 2);
el.append(title, pre);
appEl?.appendChild(el);
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

if (code && state) {
const response = await oidcClient.token.exchange(code, state);
displayTokenResponse(response);
const response = await oidcClient.token?.exchange(code, state);
if (response) {
displayTokenResponse(response);
}
}
}
Loading
Loading