Skip to content
This repository has been archived by the owner on Feb 25, 2019. It is now read-only.

feat(RPInitiatedLogoutRequest): First draft of RP Initiated Logout Request handling #15

Merged
merged 3 commits into from
Jan 12, 2017
Merged
Show file tree
Hide file tree
Changes from 2 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
13 changes: 13 additions & 0 deletions src/Provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const DynamicRegistrationRequest = require('./handlers/DynamicRegistrationReques
const JWKSetRequest = require('./handlers/JWKSetRequest')
const TokenRequest = require('./handlers/TokenRequest')
const UserInfoRequest = require('./handlers/UserInfoRequest')
const RPInitiatedLogoutRequest = require('./handlers/RPInitiatedLogoutRequest')

/**
* OpenID Connect Provider
Expand Down Expand Up @@ -123,6 +124,18 @@ class Provider extends JSONDocument {
AuthenticationRequest.handle(req, res, this)
}

/**
* Logout
*
* Bound to the OP's `end_session_endpoint` uri
*
* @param req {HTTPRequest}
* @param res {HTTPResponse}
*/
logout (req, res) {
RPInitiatedLogoutRequest.handle(req, res, this)
}

/**
* Discover
*
Expand Down
4 changes: 2 additions & 2 deletions src/handlers/AuthenticationRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ class AuthenticationRequest extends BaseRequest {
Promise
.resolve(request)
.then(request.validate)
.then(host.authenticate) // QUESTION, what's the cleanest way to
.then(host.obtainConsent) // bind this here?
.then(host.authenticate)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Binds just fine the way it is.

.then(host.obtainConsent)
.then(request.authorize)
.catch(request.internalServerError)
}
Expand Down
8 changes: 8 additions & 0 deletions src/handlers/DynamicRegistrationRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ class DynamicRegistrationRequest extends BaseRequest {
registration.client_secret = request.secret()
}

/**
* TODO: Validate that the `frontchannel_logout_uri` domain and port is the same as one of the `redirect_uris` values
* @see https://openid.net/specs/openid-connect-frontchannel-1_0.html#RPLogout
*
* The domain, port, and scheme of this URL MUST be the same as that of a
* registered Redirection URI value.
*/

// initialize and validate a client
let client = new Client(registration)
let validation = client.validate()
Expand Down
155 changes: 155 additions & 0 deletions src/handlers/RPInitiatedLogoutRequest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
'use strict'

/**
* Dependencies
* @ignore
*/
const qs = require('qs')
const BaseRequest = require('./BaseRequest')

class RPInitiatedLogoutRequest extends BaseRequest {
/**
* RP Initiated Logout Request Handler
*
* @see https://openid.net/specs/openid-connect-session-1_0.html#RPLogout
* @see https://openid.net/specs/openid-connect-frontchannel-1_0.html#RPInitiated
* @see https://openid.net/specs/openid-connect-backchannel-1_0.html#RPInitiated
*
* @param req {HTTPRequest}
* @param res {HTTPResponse}
* @param provider {Provider}
* @returns {Promise}
*/
static handle (req, res, provider) {
let { host } = provider
let request = new RPInitiatedLogoutRequest(req, res, provider)

Promise
.resolve(request)
.then(request.validate)

// From: https://openid.net/specs/openid-connect-session-1_0.html#RPLogout
// At the logout endpoint, the OP SHOULD ask the End-User whether they want
// to log out of the OP as well. If the End-User says "yes", then the OP
// MUST log out the End-User.
.then(host.logout)

.then(request.redirectOrRespond)
.catch(request.internalServerError)
}

/**
* Constructor
*
* Session spec defines the following params to the RP Initiated Logout request:
* - `id_token_hint`
* - `post_logout_redirect_uri`
* - `state`
*
* @param req {HTTPRequest}
* @param res {HTTPResponse}
* @param provider {Provider}
*/
constructor (req, res, provider) {
super(req, res, provider)
this.params = RPInitiatedLogoutRequest.getParams(this)
}

/**
* Validate
*
* @see https://openid.net/specs/openid-connect-session-1_0.html#RPLogout
*
* @param request {RPInitiatedLogoutRequest}
*/
validate (request) {
/**
* `state` parameter - no validation need it. Will be passed back to the RP
* in the `redirectToRP()` step.
*/

/**
* TODO: Validate `id_token_hint` (validate as ID Token *except* for `aud`)
*
* RECOMMENDED. Previously issued ID Token passed to the logout endpoint as
* a hint about the End-User's current authenticated session with the Client.
* This is used as an indication of the identity of the End-User that the RP
* is requesting be logged out by the OP. The OP *need not* be listed as an
* audience of the ID Token when it is used as an `id_token_hint` value.
*/

/**
* TODO: Validate that `post_logout_redirect_uri` has been registered
*
* The value MUST have been previously registered with the OP, either using
* the `post_logout_redirect_uris` Registration parameter
* or via another mechanism.
*
* Question: what's this about 'another mechanism'?
*/
}

/**
* Redirect to RP or Respond
*
* In some cases, the RP will request that the End-User's User Agent to be
* redirected back to the RP after a logout has been performed. Post-logout
* redirection is only done when the logout is RP-initiated, in which case the
* redirection target is the `post_logout_redirect_uri` query parameter value
* used by the initiating RP; otherwise it is not done.
*
* @see https://openid.net/specs/openid-connect-session-1_0.html#RedirectionAfterLogout
*
* @returns {null}
*/
redirectOrRespond () {
let { params: { post_logout_redirect_uri: postLogoutRedirectUri } } = this

if (postLogoutRedirectUri) {
this.redirectToRP()
} else {
this.respond()
}
}

/**
* Redirect To RP
*
* Redirects the user-agent back to the RP, if requested (by the RP providing
* a `post_logout_redirect_uri` parameter). Also passes through the `state`
* parameter, if supplied by the RP.
*
* @returns {null}
*/
redirectToRP () {
let { res, params: { post_logout_redirect_uri: uri, state } } = this

if (state) {
let response = qs.stringify({ state })
uri = `${uri}?${response}`
}

res.redirect(uri) // 302 redirect
}

/**
* Respond
*
* Responds to the RP Initiated Logout request with a 204 No Content, if the
* RP did not supply a `post_logout_redirect_uri` parameter.
*
* @returns {null}
*/
respond () {
let { res } = this

res.set({
'Cache-Control': 'no-store',
'Pragma': 'no-cache'
})

res.status(204).send()
}
}

module.exports = RPInitiatedLogoutRequest
29 changes: 28 additions & 1 deletion src/schemas/ClientSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const schema = new JSONSchema({
},

/**
* application_typepost_logout_redirect_uris
* application_type
* OPTIONAL. Kind of the application. The default, if omitted, is web. The
* defined values are native or web. Web Clients using the OAuth Implicit
* Grant Type MUST only register URLs using the https scheme as
Expand Down Expand Up @@ -245,6 +245,33 @@ const schema = new JSONSchema({
items: {
format: 'uri'
}
},

/**
* frontchannel_logout_uri
*
* OPTIONAL. RP URL that will cause the RP to log itself out when rendered
* in an iframe by the OP.
*
* @see https://openid.net/specs/openid-connect-frontchannel-1_0.html#RPLogout
*/
frontchannel_logout_uri: {
type: 'string',
format: 'uri'
},

/**
* frontchannel_logout_session_required
*
* OPTIONAL. Boolean value specifying whether the RP requires that `iss`
* (issuer) and `sid` (session ID) query parameters be included to identify
* the RP session with the OP when the `frontchannel_logout_uri` is used.
*
* @see https://openid.net/specs/openid-connect-frontchannel-1_0.html#RPLogout
*/
frontchannel_logout_session_required: {
type: 'boolean',
default: false
}
},
required: ['redirect_uris']
Expand Down
28 changes: 28 additions & 0 deletions test/handlers/RPInitiatedLogoutRequestSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict'

// const chai = require('chai')
// const expect = chai.expect

const RPInitiatedLogoutRequest = require('../../src/handlers/RPInitiatedLogoutRequest')

describe('RPInitiatedLogoutRequest', () => {
describe('handle()', () => {
it('should validate the request')
it('should invoke injected host.logout')
it('should invoke redirectOrRespond() if validated')
})

describe('constructor()', () => {
it('should parse the incoming request params')
})

describe('redirectOrRespond()', () => {
it('should redirect to RP if logout uri provided')
it('should respond with a 204 if no logout uri provided')
})

describe('redirectToRP()', () => {
it('should redirect to provided URI')
it('should pass through the state param')
})
})