@solid/oidc-auth-manager
Version:
An OpenID Connect (OIDC) authentication manager (OP, RP and RS) for decentralized peer-to-peer authentication
168 lines (145 loc) • 4.34 kB
JavaScript
'use strict'
const url = require('url')
class AuthCallbackRequest {
constructor (options) {
this.requestUri = options.requestUri
this.issuer = options.issuer
this.oidcManager = options.oidcManager
this.response = options.response
this.session = options.session
this.returnToUrl = options.returnToUrl || '/'
this.serverUri = options.serverUri
this.debug = options.debug || console.log.bind(console)
}
/**
* Usage:
*
* ```
* router.get('/api/oidc/rp/:issuer_id', AuthCallbackRequest.get)
* ```
*
* @param req {IncomingRequest}
* @param res {ServerResponse}
* @param next {Function}
*
* @returns {Promise}
*/
static get (req, res, next) {
const request = AuthCallbackRequest.fromParams(req, res)
return AuthCallbackRequest.handle(request)
.catch(error => {
request.debug('Error in AuthCallbackRequest.get:', error)
next(error)
})
}
/**
* Factory method, creates and returns an initialized and validated instance
* of AuthCallbackRequest from a redirected GET request.
*
* @param req {IncomingRequest}
*
* @param res {ServerResponse}
* @return {AuthCallbackRequest}
*/
static fromParams (req, res) {
let oidcManager, serverUri
if (req.app && req.app.locals) {
const locals = req.app.locals
oidcManager = locals.oidc
serverUri = locals.host.serverUri
}
const requestUri = AuthCallbackRequest.fullUriFor(req)
const issuer = AuthCallbackRequest.extractIssuer(req)
const options = {
issuer,
requestUri,
oidcManager,
serverUri,
returnToUrl: req.session.returnToUrl,
response: res,
session: req.session
}
const request = new AuthCallbackRequest(options)
return request
}
static fullUriFor (req) {
return url.format({
protocol: req.protocol,
host: req.get('host'),
pathname: req.path,
query: req.query
})
}
// Exchange authorization code for id token
static handle (request) {
return Promise.resolve()
.then(() => request.validate())
.then(() => request.loadClient())
.then(rpClient => request.validateResponse(rpClient))
.then(session => request.initSessionUserAuth(session))
.then(() => request.resumeUserWorkflow())
}
static extractIssuer (req) {
return req.params && decodeURIComponent(req.params.issuer_id)
}
validate () {
if (!this.issuer) {
const error = new Error('Issuer id is missing from request params')
error.statusCode = 400
throw error
}
}
loadClient () {
const rpClientStore = this.oidcManager.clients
return rpClientStore.clientForIssuer(this.issuer)
}
/**
* @param rpSession {Session} RelyingParty Session object
*
* @returns {Promise}
*/
async initSessionUserAuth (rpSession) {
try {
const webId = await this.oidcManager.webIdFromClaims(rpSession.idClaims)
this.session.userId = webId
this.session.credentials = {
webId,
idClaims: rpSession.idClaims,
authorization: rpSession.authorization
}
} catch (err) {
const error = new Error('Could not verify Web ID from token claims')
error.statusCode = 401
error.cause = err
error.info = { credentials: this.session.credentials }
throw error
}
}
/**
* Validates the authentication response and decodes the credentials.
* Also performs auth code exchange (trading an authorization code for an
* id token and access token), if applicable.
*
* @param client {RelyingParty}
*
* @return {Session} Containing the `idToken` and `accessToken` properties
*/
validateResponse (client) {
return client.validateResponse(this.requestUri, this.session)
.catch(error => {
error.statusCode = 400
console.log('Error in callback/validateResponse:', error)
throw error
})
}
/**
* Redirects the user back to their original requested resource, at the end
* of the OIDC authentication process.
*/
resumeUserWorkflow () {
this.debug(' Resuming workflow, redirecting to ' + this.returnToUrl)
delete this.session.returnToUrl
return this.response.redirect(302, this.returnToUrl)
}
}
module.exports = AuthCallbackRequest