Skip to content

Commit 1416ec3

Browse files
authored
Merge pull request #376 from ForgeRock/SDKS-4348-token-revoke
SDKS-4348: Implement token.revoke in OIDC client
2 parents 52ba723 + c48d9c8 commit 1416ec3

File tree

12 files changed

+405
-151
lines changed

12 files changed

+405
-151
lines changed

.changeset/chubby-chairs-dream.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@forgerock/oidc-client': minor
3+
---
4+
5+
Implement token `revoke` method

e2e/oidc-app/src/ping-am/index.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
<title>E2E Test | Ping Identity JavaScript SDK</title>
55

66
<style>
7-
#logout,
8-
#userinfo {
7+
#logout {
98
display: none;
109
}
1110
</style>
@@ -19,7 +18,8 @@ <h1>OIDC App | PingAM Login</h1>
1918
<button id="get-tokens">Get Tokens</button>
2019
<button id="renew-tokens">Renew Tokens</button>
2120
<button id="logout">Logout</button>
22-
<button id="userinfo">User Info</button>
21+
<button id="user-info-btn">User Info</button>
22+
<button id="revoke">Revoke Token</button>
2323
<a href="/ping-am/">Start Over</a>
2424
</div>
2525
<script type="module" src="./main.ts"></script>

e2e/oidc-app/src/ping-one/index.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
<title>E2E Test | Ping Identity JavaScript SDK</title>
55

66
<style>
7-
#logout,
8-
#userinfo {
7+
#logout {
98
display: none;
109
}
1110
</style>
@@ -19,7 +18,8 @@ <h1>OIDC App | P1 Login</h1>
1918
<button id="get-tokens">Get Tokens</button>
2019
<button id="renew-tokens">Renew Tokens</button>
2120
<button id="logout">Logout</button>
22-
<button id="userinfo">User Info</button>
21+
<button id="user-info-btn">User Info</button>
22+
<button id="revoke">Revoke Token</button>
2323
<a href="/ping-one/">Start Over</a>
2424
</div>
2525
<script type="module" src="./main.ts"></script>

e2e/oidc-app/src/utils/oidc-app.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ function displayTokenResponse(
2727
response: OauthTokens | TokenExchangeErrorResponse | GenericError | AuthorizationError,
2828
) {
2929
const appEl = document.getElementById('app');
30-
if ('error' in response) {
30+
if ('error' in response || !('accessToken' in response)) {
3131
console.error('Token Error:', response);
3232
displayError(response);
3333
} else {
3434
console.log('Token Response:', response);
3535
document.getElementById('logout').style.display = 'block';
36-
document.getElementById('userinfo').style.display = 'block';
36+
document.getElementById('user-info-btn').style.display = 'block';
3737
document.getElementById('login-background').style.display = 'none';
3838
document.getElementById('login-redirect').style.display = 'none';
3939

@@ -108,7 +108,7 @@ export async function oidcApp({ config, urlParams }) {
108108
displayTokenResponse(response);
109109
});
110110

111-
document.getElementById('userinfo').addEventListener('click', async () => {
111+
document.getElementById('user-info-btn').addEventListener('click', async () => {
112112
const userInfo = await oidcClient.user.info();
113113

114114
if ('error' in userInfo) {
@@ -124,16 +124,30 @@ export async function oidcApp({ config, urlParams }) {
124124
}
125125
});
126126

127+
document.getElementById('revoke').addEventListener('click', async () => {
128+
const response = await oidcClient.token.revoke();
129+
130+
if ('error' in response) {
131+
console.error('Token Revocation Error:', response);
132+
displayError(response);
133+
} else {
134+
const appEl = document.getElementById('app');
135+
const userInfoEl = document.createElement('div');
136+
userInfoEl.innerHTML = `<p>Token successfully revoked</p>`;
137+
appEl.appendChild(userInfoEl);
138+
}
139+
});
140+
127141
document.getElementById('logout').addEventListener('click', async () => {
128142
const response = await oidcClient.user.logout();
129143

130-
if (response && 'error' in response) {
144+
if ('error' in response) {
131145
console.error('Logout Error:', response);
132146
displayError(response);
133147
} else {
134148
console.log('Logout successful');
135149
document.getElementById('logout').style.display = 'none';
136-
document.getElementById('userinfo').style.display = 'none';
150+
document.getElementById('user-info-btn').style.display = 'none';
137151
document.getElementById('login-background').style.display = 'block';
138152
document.getElementById('login-redirect').style.display = 'block';
139153
window.location.assign(window.location.origin + window.location.pathname);

e2e/oidc-suites/src/login.spec.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ test.describe('PingAM login and get token tests', () => {
3030
expect(page.url()).toContain('code');
3131
expect(page.url()).toContain('state');
3232
await expect(page.locator('#accessToken-0')).not.toBeEmpty();
33-
await expect(page.locator('#accessToken-0')).not.toHaveText('undefined');
3433
});
3534

3635
test('redirect login with valid credentials', async ({ page }) => {
@@ -47,7 +46,6 @@ test.describe('PingAM login and get token tests', () => {
4746
expect(page.url()).toContain('code');
4847
expect(page.url()).toContain('state');
4948
await expect(page.locator('#accessToken-0')).not.toBeEmpty();
50-
await expect(page.locator('#accessToken-0')).not.toHaveText('undefined');
5149
});
5250

5351
test('background login with invalid client id fails', async ({ page }) => {
@@ -81,7 +79,6 @@ test.describe('PingOne login and get token tests', () => {
8179
expect(page.url()).toContain('code');
8280
expect(page.url()).toContain('state');
8381
await expect(page.locator('#accessToken-0')).not.toBeEmpty();
84-
await expect(page.locator('#accessToken-0')).not.toHaveText('undefined');
8582
});
8683

8784
test('redirect login with valid credentials', async ({ page }) => {
@@ -99,7 +96,6 @@ test.describe('PingOne login and get token tests', () => {
9996
expect(page.url()).toContain('code');
10097
expect(page.url()).toContain('state');
10198
await expect(page.locator('#accessToken-0')).not.toBeEmpty();
102-
await expect(page.locator('#accessToken-0')).not.toHaveText('undefined');
10399
});
104100

105101
test('login with invalid client id fails', async ({ page }) => {
@@ -140,7 +136,6 @@ test.describe('PingOne login and get token tests', () => {
140136
expect(page.url()).toContain('code');
141137
expect(page.url()).toContain('state');
142138
await expect(page.locator('#accessToken-0')).not.toBeEmpty();
143-
await expect(page.locator('#accessToken-0')).not.toHaveText('undefined');
144139
});
145140
});
146141

e2e/oidc-suites/src/token.spec.ts

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,6 @@ import {
1515
} from './utils/demo-users.js';
1616
import { asyncEvents } from './utils/async-events.js';
1717

18-
test('get tokens without logging in should error', async ({ page }) => {
19-
const { navigate } = asyncEvents(page);
20-
await navigate('/ping-am/');
21-
expect(page.url()).toBe('http://localhost:8443/ping-am/');
22-
23-
await page.getByRole('button', { name: 'Get Tokens' }).click();
24-
25-
await expect(page.locator('.error')).toContainText(`"error": "No tokens found"`);
26-
await expect(page.locator('.error')).toContainText(`"type": "state_error"`);
27-
});
28-
2918
test.describe('PingAM tokens', () => {
3019
test('login and get tokens', async ({ page }) => {
3120
const { navigate, clickButton } = asyncEvents(page);
@@ -44,7 +33,6 @@ test.describe('PingAM tokens', () => {
4433

4534
await page.getByRole('button', { name: 'Get Tokens' }).click();
4635
await expect(page.locator('#accessToken-1')).not.toBeEmpty();
47-
await expect(page.locator('#accessToken-1')).not.toHaveText('undefined');
4836

4937
const accessToken0 = await page.locator('#accessToken-0').textContent();
5038
const accessToken1 = await page.locator('#accessToken-1').textContent();
@@ -70,13 +58,35 @@ test.describe('PingAM tokens', () => {
7058
await page.getByRole('button', { name: 'Renew Tokens' }).click();
7159

7260
await expect(page.locator('#accessToken-1')).not.toBeEmpty();
73-
await expect(page.locator('#accessToken-1')).not.toHaveText('undefined');
7461

7562
const accessToken0 = await page.locator('#accessToken-0').textContent();
7663
const accessToken1 = await page.locator('#accessToken-1').textContent();
7764
await expect(accessToken0).not.toBe(accessToken1);
7865
});
7966

67+
test('login and revoke tokens', async ({ page }) => {
68+
const { navigate, clickButton } = asyncEvents(page);
69+
await navigate('/ping-am/');
70+
expect(page.url()).toBe('http://localhost:8443/ping-am/');
71+
72+
await clickButton('Login (Background)', 'https://openam-sdks.forgeblocks.com/');
73+
74+
await page.getByLabel('User Name').fill(pingAmUsername);
75+
await page.getByRole('textbox', { name: 'Password' }).fill(pingAmPassword);
76+
await page.getByRole('button', { name: 'Next' }).click();
77+
78+
await page.waitForURL('http://localhost:8443/ping-am/**', { waitUntil: 'networkidle' });
79+
expect(page.url()).toContain('code');
80+
expect(page.url()).toContain('state');
81+
82+
await expect(page.locator('#accessToken-0')).not.toBeEmpty();
83+
84+
await page.getByRole('button', { name: 'Revoke Token' }).click();
85+
await expect(page.getByText('Token successfully revoked')).toBeVisible();
86+
const token = await page.evaluate(() => localStorage.getItem('pic-oidcTokens'));
87+
await expect(token).toBeFalsy();
88+
});
89+
8090
test('renew tokens without logging in should error', async ({ page }) => {
8191
const { navigate } = asyncEvents(page);
8292
await navigate('/ping-am/');
@@ -110,7 +120,6 @@ test.describe('PingOne tokens', () => {
110120

111121
await page.getByRole('button', { name: 'Get Tokens' }).click();
112122
await expect(page.locator('#accessToken-1')).not.toBeEmpty();
113-
await expect(page.locator('#accessToken-1')).not.toHaveText('undefined');
114123

115124
const accessToken0 = await page.locator('#accessToken-0').textContent();
116125
const accessToken1 = await page.locator('#accessToken-1').textContent();
@@ -136,13 +145,35 @@ test.describe('PingOne tokens', () => {
136145
await page.getByRole('button', { name: 'Renew Tokens' }).click();
137146

138147
await expect(page.locator('#accessToken-1')).not.toBeEmpty();
139-
await expect(page.locator('#accessToken-1')).not.toHaveText('undefined');
140148

141149
const accessToken0 = await page.locator('#accessToken-0').textContent();
142150
const accessToken1 = await page.locator('#accessToken-1').textContent();
143151
await expect(accessToken0).not.toBe(accessToken1);
144152
});
145153

154+
test('login and revoke tokens', async ({ page }) => {
155+
const { navigate, clickButton } = asyncEvents(page);
156+
await navigate('/ping-one/');
157+
expect(page.url()).toBe('http://localhost:8443/ping-one/');
158+
159+
await clickButton('Login (Background)', 'https://apps.pingone.ca/');
160+
161+
await page.getByLabel('Username').fill(pingOneUsername);
162+
await page.getByRole('textbox', { name: 'Password' }).fill(pingOnePassword);
163+
await page.getByRole('button', { name: 'Sign On' }).click();
164+
165+
await page.waitForURL('http://localhost:8443/ping-one/**', { waitUntil: 'networkidle' });
166+
expect(page.url()).toContain('code');
167+
expect(page.url()).toContain('state');
168+
169+
await expect(page.locator('#accessToken-0')).not.toBeEmpty();
170+
171+
await page.getByRole('button', { name: 'Revoke Token' }).click();
172+
await expect(page.getByText('Token successfully revoked')).toBeVisible();
173+
const token = await page.evaluate(() => localStorage.getItem('pic-oidcTokens'));
174+
await expect(token).toBeFalsy();
175+
});
176+
146177
test('renew tokens without logging in should error', async ({ page }) => {
147178
const { navigate } = asyncEvents(page);
148179
await navigate('/ping-one/');
@@ -155,3 +186,25 @@ test.describe('PingOne tokens', () => {
155186
await expect(page.locator('.error')).toContainText('User authentication is required');
156187
});
157188
});
189+
190+
test('get tokens without logging in should error', async ({ page }) => {
191+
const { navigate } = asyncEvents(page);
192+
await navigate('/ping-am/');
193+
expect(page.url()).toBe('http://localhost:8443/ping-am/');
194+
195+
await page.getByRole('button', { name: 'Get Tokens' }).click();
196+
197+
await expect(page.locator('.error')).toContainText(`"error": "No tokens found"`);
198+
await expect(page.locator('.error')).toContainText(`"type": "state_error"`);
199+
});
200+
201+
test('revoke tokens should error with missing token', async ({ page }) => {
202+
const { navigate } = asyncEvents(page);
203+
await navigate('/ping-am/');
204+
expect(page.url()).toBe('http://localhost:8443/ping-am/');
205+
206+
await page.getByRole('button', { name: 'Revoke Token' }).click();
207+
208+
await expect(page.locator('.error')).toContainText(`"error": "No access token found"`);
209+
await expect(page.locator('.error')).toContainText(`"type": "state_error"`);
210+
});

e2e/oidc-suites/src/user.spec.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ test.describe('User tests', () => {
2727
await page.getByRole('textbox', { name: 'Password' }).fill(pingAmPassword);
2828
await page.getByRole('button', { name: 'Next' }).click();
2929

30-
await page.waitForURL('http://localhost:8443/ping-am/**');
30+
await page.waitForURL('http://localhost:8443/ping-am/**', { waitUntil: 'networkidle' });
3131
expect(page.url()).toContain('code');
3232
expect(page.url()).toContain('state');
3333

34-
await page.getByRole('button', { name: 'User Info' }).click();
34+
await clickButton('User Info', 'https://openam-sdks.forgeblocks.com/am/oauth2/alpha/userinfo');
3535
await expect(page.locator('#userInfo')).not.toBeEmpty();
3636
await expect(page.getByText('Sdk User')).toBeVisible();
3737
await expect(page.getByText('[email protected]')).toBeVisible();
@@ -48,13 +48,28 @@ test.describe('User tests', () => {
4848
await page.getByRole('textbox', { name: 'Password' }).fill(pingOnePassword);
4949
await page.getByRole('button', { name: 'Sign On' }).click();
5050

51-
await page.waitForURL('http://localhost:8443/ping-one/**');
51+
await page.waitForURL('http://localhost:8443/ping-one/**', { waitUntil: 'networkidle' });
5252
expect(page.url()).toContain('code');
5353
expect(page.url()).toContain('state');
5454

55-
await page.getByRole('button', { name: 'User Info' }).click();
55+
await clickButton(
56+
'User Info',
57+
'https://auth.pingone.ca/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/userinfo',
58+
);
5659
await expect(page.locator('#userInfo')).not.toBeEmpty();
5760
await expect(page.getByText('demouser')).toBeVisible();
5861
await expect(page.getByText('[email protected]')).toBeVisible();
5962
});
63+
64+
test('get user info should error with missing token', async ({ page }) => {
65+
const { navigate } = asyncEvents(page);
66+
await navigate('/ping-am/');
67+
expect(page.url()).toBe('http://localhost:8443/ping-am/');
68+
69+
await page.getByRole('button', { name: 'User Info' }).click();
70+
71+
await expect(page.locator('#userInfo')).not.toBeVisible();
72+
await expect(page.locator('.error')).toContainText(`"error": "No access token found"`);
73+
await expect(page.locator('.error')).toContainText(`"type": "auth_error"`);
74+
});
6075
});

0 commit comments

Comments
 (0)