Skip to content

Commit f735d42

Browse files
committed
Handle configuration and swapping between different token types
1 parent 9358bb1 commit f735d42

4 files changed

Lines changed: 122 additions & 12 deletions

File tree

src/AccessToken.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ class AccessToken extends JWT {
6868
const key = keys.token.signing[alg].privateKey
6969
const kid = keys.token.signing[alg].publicJwk.kid
7070

71+
7172
const header = { alg, kid }
7273
const payload = { iss, aud, sub, exp, iat, jti, scope }
7374

@@ -108,7 +109,9 @@ class AccessToken extends JWT {
108109
scope = code.scope
109110
}
110111

111-
const options = { aud, sub, scope, alg, jti, iat, max }
112+
const tokenType = request.tokenType;
113+
114+
const options = { aud, sub, scope, alg, jti, iat, max, tokenType }
112115

113116
let header, payload
114117

src/Provider.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const DEFAULT_RESPONSE_TYPES_SUPPORTED = [
1717
'code',
1818
'code token',
1919
'code id_token',
20+
'id_token code',
2021
'id_token',
2122
'id_token token',
2223
'code id_token token',
@@ -34,6 +35,8 @@ const DEFAULT_GRANT_TYPES_SUPPORTED = [
3435
]
3536
const DEFAULT_SUBJECT_TYPES_SUPPORTED = ['public']
3637

38+
const DEFAULT_TOKEN_TYPES_SUPPORTED = ['legacyPop', 'dpop']
39+
3740
/**
3841
* OpenID Connect Provider
3942
*/
@@ -53,6 +56,8 @@ class Provider {
5356
this.scopes_supported = data.scopes_supported
5457
this.response_types_supported = data.response_types_supported ||
5558
DEFAULT_RESPONSE_TYPES_SUPPORTED
59+
this.token_types_supported = data.token_types_supported ||
60+
DEFAULT_TOKEN_TYPES_SUPPORTED
5661
this.response_modes_supported = data.response_modes_supported ||
5762
DEFAULT_RESPONSE_MODES_SUPPORTED
5863
this.grant_types_supported = data.grant_types_supported ||

src/handlers/AuthenticationRequest.js

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ const { JWT, JWK, JWKSet } = require('@solid/jose')
1212
const { random } = require('../crypto')
1313
const { URL } = require('whatwg-url')
1414

15+
const LEGACY_POP = "legacyPop"
16+
const DPOP = "dpop"
17+
1518
/**
1619
* AuthenticationRequest
1720
*/
@@ -25,14 +28,54 @@ class AuthenticationRequest extends BaseRequest {
2528
* @param {Provider} provider
2629
*/
2730
static handle (req, res, provider) {
28-
let {host} = provider
31+
let {host, token_types_supported} = provider
2932
let request = new AuthenticationRequest(req, res, provider)
3033

34+
return Promise
35+
.resolve(request)
36+
.then(request.getTokenType)
37+
.then((request) => {
38+
if (
39+
request.tokenType === LEGACY_POP &&
40+
token_types_supported.includes(LEGACY_POP)
41+
) {
42+
return request.handleLegacyPop(request, host)
43+
} else if (
44+
request.tokenType === DPOP &&
45+
token_types_supported.includes(DPOP)
46+
) {
47+
return request.handleDpop(request, host)
48+
} else {
49+
return request.badRequest({
50+
error: 'invalid_request',
51+
error_description: `This server does not allow a token of type ${request.tokenType}`,
52+
})
53+
}
54+
})
55+
}
56+
57+
handleLegacyPop (request, host) {
58+
console.log("Handling legacy Pop")
3159
return Promise
3260
.resolve(request)
3361
.then(request.loadClient)
3462
.then(request.decodeRequestParam)
35-
.then(request.validate)
63+
.then(request.validateCommon)
64+
.then(request.validateLegacyPop)
65+
.then(host.authenticate)
66+
.then(host.obtainConsent)
67+
.then(request.authorize)
68+
.catch(err => request.error(err))
69+
}
70+
71+
handleDpop (request, host) {
72+
console.log("Handling dpop")
73+
request.responseMode = '?'
74+
return Promise
75+
.resolve(request)
76+
.then(request.loadClient)
77+
.then(request.validateCommon)
78+
.then(request.validateDpop)
3679
.then(host.authenticate)
3780
.then(host.obtainConsent)
3881
.then(request.authorize)
@@ -53,6 +96,15 @@ class AuthenticationRequest extends BaseRequest {
5396
this.responseMode = AuthenticationRequest.getResponseMode(this)
5497
}
5598

99+
getTokenType (request) {
100+
if (request.params.request) {
101+
request.tokenType = LEGACY_POP
102+
} else {
103+
request.tokenType = DPOP
104+
}
105+
return request;
106+
}
107+
56108
/**
57109
* loadClient
58110
*
@@ -353,7 +405,7 @@ class AuthenticationRequest extends BaseRequest {
353405
*
354406
* @returns {AuthenticationRequest}
355407
*/
356-
validate (request) {
408+
validateCommon (request) {
357409
const { params, client } = request
358410

359411
// CLIENT ID IS REQUIRED
@@ -412,14 +464,6 @@ class AuthenticationRequest extends BaseRequest {
412464
})
413465
}
414466

415-
// NONCE MAY BE REQUIRED
416-
if (!request.requiredNonceProvided()) {
417-
return request.redirect({
418-
error: 'invalid_request',
419-
error_description: 'Missing nonce'
420-
})
421-
}
422-
423467
// RESPONSE TYPE MUST BE SUPPORTED
424468
// TODO is this something the client can configure too?
425469
if (!request.supportedResponseType()) {
@@ -442,6 +486,22 @@ class AuthenticationRequest extends BaseRequest {
442486
return request
443487
}
444488

489+
validateLegacyPop (request) {
490+
// NONCE MAY BE REQUIRED
491+
if (!request.requiredNonceProvided()) {
492+
return request.redirect({
493+
error: 'invalid_request',
494+
error_description: 'Missing nonce'
495+
})
496+
}
497+
498+
return request
499+
}
500+
501+
validateDpop (request) {
502+
return request
503+
}
504+
445505
static validateRedirectUri (registeredUris, redirectUri) {
446506
// Drop hash fragment when validating against pre-registered uris
447507
let uriNoHash = (uri) => {

src/handlers/TokenRequest.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ const AccessToken = require('../AccessToken')
99
const AuthorizationCode = require('../AuthorizationCode')
1010
const IDToken = require('../IDToken')
1111

12+
const LEGACY_POP = "legacyPop"
13+
const DPOP = "dpop"
14+
1215
/**
1316
* TokenRequest
1417
*/
@@ -27,6 +30,7 @@ class TokenRequest extends BaseRequest {
2730
Promise
2831
.resolve(request)
2932
.then(request.validate)
33+
.then(request.extractDpopHeader)
3034
.then(request.authenticateClient)
3135
.then(request.verifyAuthorizationCode)
3236
.then(request.grant)
@@ -40,6 +44,7 @@ class TokenRequest extends BaseRequest {
4044
super(req, res, provider)
4145
this.params = TokenRequest.getParams(this)
4246
this.grantType = TokenRequest.getGrantType(this)
47+
this.tokenType = TokenRequest.getTokenType(this)
4348
}
4449

4550
/**
@@ -53,6 +58,15 @@ class TokenRequest extends BaseRequest {
5358
return params.grant_type
5459
}
5560

61+
static getTokenType (request) {
62+
const { req } = request;
63+
if (req.headers && req.headers.dpop) {
64+
return DPOP;
65+
} else {
66+
return LEGACY_POP;
67+
}
68+
}
69+
5670
/**
5771
* Validate Request
5872
*
@@ -176,6 +190,10 @@ class TokenRequest extends BaseRequest {
176190
method = 'clientSecretJWT'
177191
}
178192

193+
if (req.body && req.body.client_id) {
194+
method = 'clientId'
195+
}
196+
179197
// Missing authentication parameters
180198
if (!method) {
181199
return request.badRequest({
@@ -349,6 +367,22 @@ class TokenRequest extends BaseRequest {
349367
})
350368
}
351369

370+
clientId (request) {
371+
const { req: { body: { client_id: clientId } }, provider} = request
372+
return provider.backend.get('clients', clientId)
373+
.then(client => {
374+
if (!client) {
375+
return request.badRequest({
376+
error: 'unauthorized_client',
377+
error_description: 'Unknown client'
378+
})
379+
}
380+
request.client = client
381+
382+
return request
383+
})
384+
}
385+
352386
/**
353387
* Private Key JWT Authentication
354388
*/
@@ -359,6 +393,14 @@ class TokenRequest extends BaseRequest {
359393
*/
360394
// none () {}
361395

396+
extractDpopHeader (request) {
397+
if (request.tokenType === DPOP) {
398+
const decodedDpop = JWT.decode(req.headers.dpop);
399+
return request;
400+
}
401+
return request;
402+
}
403+
362404
/**
363405
* Grant
364406
*

0 commit comments

Comments
 (0)