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

Commit

Permalink
feat(RPInitiatedLogoutRequest): First draft of RP Initiated Logout Re…
Browse files Browse the repository at this point in the history
…quest handling
  • Loading branch information
dmitrizagidulin committed Jan 11, 2017
1 parent 6fa15ea commit 4e1c115
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 3 deletions.
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)
.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
156 changes: 156 additions & 0 deletions src/handlers/RPInitiatedLogoutRequest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
'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
*
* @param request {RPInitiatedLogoutRequest}
* @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

0 comments on commit 4e1c115

Please sign in to comment.