-
Notifications
You must be signed in to change notification settings - Fork 141
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
37f8082
commit 96f1226
Showing
30 changed files
with
1,185 additions
and
108 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
const { assert } = require('chai'); | ||
const puppeteer = require('puppeteer'); | ||
const request = require('request-promise-native'); | ||
const provider = require('./fixture/oidc-provider'); | ||
const { | ||
baseUrl, | ||
start, | ||
runExample, | ||
stubEnv, | ||
checkContext, | ||
goto, | ||
login, | ||
} = require('./fixture/helpers'); | ||
|
||
describe('back-channel logout', async () => { | ||
let authServer; | ||
let appServer; | ||
let browser; | ||
|
||
beforeEach(async () => { | ||
stubEnv(); | ||
authServer = await start(provider, 3001); | ||
}); | ||
|
||
afterEach(async () => { | ||
authServer.close(); | ||
appServer.close(); | ||
await browser.close(); | ||
}); | ||
|
||
const runTest = async (example) => { | ||
appServer = await runExample(example); | ||
browser = await puppeteer.launch({ | ||
args: ['no-sandbox', 'disable-setuid-sandbox'], | ||
}); | ||
const page = await browser.newPage(); | ||
await goto(baseUrl, page); | ||
assert.match(page.url(), /http:\/\/localhost:300/); | ||
await Promise.all([page.click('a'), page.waitForNavigation()]); | ||
await login('username', 'password', page); | ||
assert.equal( | ||
page.url(), | ||
`${baseUrl}/`, | ||
'User is returned to the original page' | ||
); | ||
const loggedInCookies = await page.cookies('http://localhost:3000'); | ||
assert.ok(loggedInCookies.find(({ name }) => name === 'appSession')); | ||
|
||
const response = await checkContext(await page.cookies()); | ||
assert.isOk(response.isAuthenticated); | ||
|
||
await goto(`${baseUrl}/logout-token`, page); | ||
|
||
await page.waitForSelector('pre'); | ||
const element = await page.$('pre'); | ||
const curl = await page.evaluate((el) => el.textContent, element); | ||
const [, logoutToken] = curl.match(/logout_token=([^"]+)/); | ||
const res = await request.post('http://localhost:3000/backchannel-logout', { | ||
form: { | ||
logout_token: logoutToken, | ||
}, | ||
resolveWithFullResponse: true, | ||
}); | ||
assert.equal(res.statusCode, 204); | ||
|
||
await goto(baseUrl, page); | ||
const loggedOutCookies = await page.cookies('http://localhost:3000'); | ||
assert.notOk(loggedOutCookies.find(({ name }) => name === 'appSession')); | ||
}; | ||
|
||
it('should logout via back-channel logout', () => | ||
runTest('backchannel-logout')); | ||
|
||
it('should not logout sub via back-channel logout if user logs in after', async () => { | ||
await runTest('backchannel-logout'); | ||
|
||
await browser.close(); | ||
browser = await puppeteer.launch({ | ||
args: ['no-sandbox', 'disable-setuid-sandbox'], | ||
}); | ||
const page = await browser.newPage(); | ||
await goto(baseUrl, page); | ||
assert.match(page.url(), /http:\/\/localhost:300/); | ||
await Promise.all([page.click('a'), page.waitForNavigation()]); | ||
await login('username', 'password', page); | ||
assert.equal( | ||
page.url(), | ||
`${baseUrl}/`, | ||
'User is returned to the original page' | ||
); | ||
|
||
const loggedInCookies = await page.cookies('http://localhost:3000'); | ||
assert.ok(loggedInCookies.find(({ name }) => name === 'appSession')); | ||
const response = await checkContext(await page.cookies()); | ||
assert.isOk(response.isAuthenticated); | ||
}); | ||
|
||
it('should logout via back-channel logout with custom implementation genid', () => | ||
runTest('backchannel-logout-custom-genid')); | ||
|
||
it('should logout via back-channel logout with custom implementation query store', () => | ||
runTest('backchannel-logout-custom-query-store')); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
const { JWK } = require('jose'); | ||
|
||
const key = JWK.generateSync('RSA', 2048, { | ||
alg: 'RS256', | ||
kid: 'key-1', | ||
use: 'sig', | ||
}); | ||
|
||
module.exports.privateJWK = key.toJWK(true); | ||
module.exports.publicJWK = key.toJWK(); | ||
module.exports.privatePEM = key.toPEM(true); | ||
module.exports.publicPEM = key.toPEM(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
const { promisify } = require('util'); | ||
const crypto = require('crypto'); | ||
const express = require('express'); | ||
const { auth, requiresAuth } = require('../'); | ||
const { logoutTokenTester } = require('../end-to-end/fixture/helpers'); | ||
|
||
// This custom implementation uses a sessions with an id that matches the | ||
// Identity Provider's session id "sid" (by using the "genid" config). | ||
// When the SDK receives a logout token, it can identify the session that needs | ||
// to be destroyed by the logout token's "sid". | ||
|
||
const MemoryStore = require('memorystore')(auth); | ||
|
||
const app = express(); | ||
|
||
const store = new MemoryStore(); | ||
const destroy = promisify(store.destroy).bind(store); | ||
|
||
const onLogoutToken = async (token) => { | ||
const { sid } = token; | ||
// Delete the session - no need to store a logout token. | ||
await destroy(sid); | ||
}; | ||
|
||
app.use( | ||
auth({ | ||
clientID: 'backchannel-logout-client', | ||
authRequired: false, | ||
idpLogout: true, | ||
backchannelLogout: { | ||
onLogoutToken, | ||
isLoggedOut: false, | ||
onLogin: false, | ||
}, | ||
session: { | ||
store, | ||
// If you're using a custom `genid` you should sign the session store cookie | ||
// to ensure it is a cryptographically secure random string and not guessable. | ||
signSessionStoreCookie: true, | ||
genid(req) { | ||
if (req.oidc && req.oidc.isAuthenticated()) { | ||
const { sid } = req.oidc.idTokenClaims; | ||
// Note this must be unique and a cryptographically secure random value. | ||
return sid; | ||
} else { | ||
// Anonymous user sessions (like checkout baskets) | ||
return crypto.randomBytes(16).toString('hex'); | ||
} | ||
}, | ||
}, | ||
}) | ||
); | ||
|
||
app.get('/', async (req, res) => { | ||
if (req.oidc.isAuthenticated()) { | ||
res.send(`hello ${req.oidc.user.sub} <a href="/logout">logout</a>`); | ||
} else { | ||
res.send('<a href="/login">login</a>'); | ||
} | ||
}); | ||
|
||
// For testing purposes only | ||
app.get( | ||
'/logout-token', | ||
requiresAuth(), | ||
logoutTokenTester('backchannel-logout-client', true) | ||
); | ||
|
||
module.exports = app; |
Oops, something went wrong.