Skip to content

Commit

Permalink
Provide option to skip idToken signature validation (#131)
Browse files Browse the repository at this point in the history
  • Loading branch information
jmelberg-okta committed Aug 7, 2018
1 parent 6ef08e1 commit 123b3e2
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 24 deletions.
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ tokenManager: {
| `redirectUri` | The url that is redirected to when using `token.getWithRedirect`. This must be pre-registered as part of client registration. If no `redirectUri` is provided, defaults to the current origin. |
| `authorizeUrl` | Specify a custom authorizeUrl to perform the OIDC flow. Defaults to the issuer plus "/v1/authorize". |
| `userinfoUrl` | Specify a custom userinfoUrl. Defaults to the issuer plus "/v1/userinfo". |
| `ignoreSignature` | Disable ID token signature validation. Defaults to `false`. |
| | **Important:** For the Implicit flow, the token signature MUST be validated per [ID token Validation](http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDTValidation). This option should be used only for browser support and testing purposes. |

##### Example Client

Expand Down Expand Up @@ -251,7 +253,7 @@ var config = {
* [token.refresh](#tokenrefreshtokentorefresh)
* [token.getUserInfo](#tokengetuserinfoaccesstokenobject)
* [token.verify](#tokenverifyidtokenobject)
* [tokenManager](#tokenManager)
* [tokenManager](#tokenmanager)
* [tokenManager.add](#tokenmanageraddkey-token)
* [tokenManager.get](#tokenmanagergetkey)
* [tokenManager.remove](#tokenmanagerremovekey)
Expand Down Expand Up @@ -1477,9 +1479,14 @@ authClient.token.getUserInfo(accessTokenObject)
Verify the validity of an ID token's claims and check the signature on browsers that support web cryptography.

* `idTokenObject` - an ID token returned by this library. note: this is not the raw ID token JWT
* `validationOptions` - Optional object to assert ID token claim values. Defaults to the configuration passed in during client instantiation.

```javascript
authClient.token.verify(idTokenObject)
var validationOptions = {
issuer: 'https://{yourOktaDomain}/oauth2/{authorizationServerId}'
}

authClient.token.verify(idTokenObject, validationOptions)
.then(function() {
// the idToken is valid
})
Expand Down
4 changes: 4 additions & 0 deletions lib/clientBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ function OktaAuthBuilder(args) {
this.options.maxClockSkew = args.maxClockSkew;
}

// Give the developer the ability to disable token signature
// validation.
this.options.ignoreSignature = !!args.ignoreSignature;

sdk.session = {
close: util.bind(session.closeSession, null, sdk),
exists: util.bind(session.sessionExists, null, sdk),
Expand Down
6 changes: 5 additions & 1 deletion lib/oauthUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,11 @@ function getKey(sdk, issuer, kid) {
});
}

function validateClaims(sdk, claims, aud, iss, nonce) {
function validateClaims(sdk, claims, validationParams) {
var aud = validationParams.clientId;
var iss = validationParams.issuer;
var nonce = validationParams.nonce;

if (!claims || !iss || !aud) {
throw new AuthSdkError('The jwt, iss, and aud arguments are all required');
}
Expand Down
27 changes: 22 additions & 5 deletions lib/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function decodeToken(token) {
return decodedToken;
}

function verifyToken(sdk, token, nonce, ignoreSignature) {
function verifyToken(sdk, token, validationParams) {
return new Q()
.then(function() {
if (!token || !token.idToken) {
Expand All @@ -48,12 +48,20 @@ function verifyToken(sdk, token, nonce, ignoreSignature) {

var jwt = decodeToken(token.idToken);

var validationOptions = {
clientId: sdk.options.clientId,
issuer: sdk.options.issuer || sdk.options.url,
ignoreSignature: sdk.options.ignoreSignature
};

util.extend(validationOptions, validationParams);

// Standard claim validation
oauthUtil.validateClaims(sdk, jwt.payload, token.clientId, token.issuer, nonce);
oauthUtil.validateClaims(sdk, jwt.payload, validationOptions);

// If the browser doesn't support native crypto or we choose not
// to verify the signature, bail early
if (ignoreSignature || !sdk.features.isTokenVerifySupported()) {
if (validationOptions.ignoreSignature == true || !sdk.features.isTokenVerifySupported()) {
return token;
}

Expand Down Expand Up @@ -168,7 +176,14 @@ function handleOAuthResponse(sdk, oauthParams, res, urls) {
clientId: clientId
};

return verifyToken(sdk, idToken, oauthParams.nonce, true)
var validationParams = {
clientId: clientId,
issuer: urls.issuer,
nonce: oauthParams.nonce,
ignoreSignature: oauthParams.ignoreSignature
};

return verifyToken(sdk, idToken, validationParams)
.then(function() {
tokenDict['id_token'] = idToken;
return tokenDict;
Expand Down Expand Up @@ -203,7 +218,8 @@ function getDefaultOAuthParams(sdk, oauthOptions) {
responseMode: 'okta_post_message',
state: util.genRandomString(64),
nonce: util.genRandomString(64),
scopes: ['openid', 'email']
scopes: ['openid', 'email'],
ignoreSignature: sdk.options.ignoreSignature
};
util.extend(defaults, oauthOptions);
return defaults;
Expand Down Expand Up @@ -481,6 +497,7 @@ function getWithRedirect(sdk, oauthOptions, options) {
state: oauthParams.state,
nonce: oauthParams.nonce,
scopes: oauthParams.scopes,
clientId: oauthParams.clientId,
urls: urls
}));

Expand Down
36 changes: 36 additions & 0 deletions test/spec/oauthUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -559,4 +559,40 @@ define(function(require) {

});

describe('validateClaims', function () {
var sdk = new OktaAuth({
url: 'https://auth-js-test.okta.com',
clientId: 'foo',
ignoreSignature: false
});

var validationOptions = {
clientId: 'foo',
issuer: 'https://auth-js-test.okta.com'
};

it('throws an AuthSdkError when no jwt is provided', function () {
var fn = function () { oauthUtil.validateClaims(sdk, undefined, validationOptions); };
expect(fn).toThrowError('The jwt, iss, and aud arguments are all required');
});

it('throws an AuthSdkError when no clientId is provided', function () {
var fn = function () {
oauthUtil.validateClaims(sdk, undefined, {
issuer: 'https://auth-js-test.okta.com'
});
};
expect(fn).toThrowError('The jwt, iss, and aud arguments are all required');
});

it('throws an AuthSdkError when no issuer is provided', function () {
var fn = function () {
oauthUtil.validateClaims(sdk, undefined, {
clientId: 'foo'
});
};
expect(fn).toThrowError('The jwt, iss, and aud arguments are all required');
});
});

});
51 changes: 35 additions & 16 deletions test/spec/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,7 @@ define(function(require) {
state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
scopes: ['openid', 'email'],
clientId: 'NPSfOkH5eZrTy8PMDlvx',
urls: {
issuer: 'https://auth-js-test.okta.com',
authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize',
Expand Down Expand Up @@ -995,6 +996,7 @@ define(function(require) {
state: oauthUtil.mockedState,
nonce: oauthUtil.mockedNonce,
scopes: ['openid', 'email'],
clientId: 'NPSfOkH5eZrTy8PMDlvx',
urls: {
issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7',
authorizeUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize',
Expand Down Expand Up @@ -1045,6 +1047,7 @@ define(function(require) {
state: oauthUtil.mockedState,
nonce: oauthUtil.mockedNonce,
scopes: ['email'],
clientId: 'NPSfOkH5eZrTy8PMDlvx',
urls: {
issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7',
authorizeUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize',
Expand Down Expand Up @@ -1088,6 +1091,7 @@ define(function(require) {
state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
scopes: ['email'],
clientId: 'NPSfOkH5eZrTy8PMDlvx',
urls: {
issuer: 'https://auth-js-test.okta.com',
authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize',
Expand Down Expand Up @@ -1136,6 +1140,7 @@ define(function(require) {
state: oauthUtil.mockedState,
nonce: oauthUtil.mockedNonce,
scopes: ['email'],
clientId: 'NPSfOkH5eZrTy8PMDlvx',
urls: {
issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7',
authorizeUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize',
Expand Down Expand Up @@ -1178,6 +1183,7 @@ define(function(require) {
state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
scopes: ['openid', 'email'],
clientId: 'NPSfOkH5eZrTy8PMDlvx',
urls: {
issuer: 'https://auth-js-test.okta.com',
authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize',
Expand Down Expand Up @@ -1225,6 +1231,7 @@ define(function(require) {
state: oauthUtil.mockedState,
nonce: oauthUtil.mockedNonce,
scopes: ['openid', 'email'],
clientId: 'NPSfOkH5eZrTy8PMDlvx',
urls: {
issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7',
authorizeUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize',
Expand Down Expand Up @@ -1267,6 +1274,7 @@ define(function(require) {
state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
scopes: ['openid', 'email'],
clientId: 'NPSfOkH5eZrTy8PMDlvx',
urls: {
issuer: 'https://auth-js-test.okta.com',
authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize',
Expand Down Expand Up @@ -1294,8 +1302,7 @@ define(function(require) {
'scope=openid%20email'
});
});

it('sets authorize url for authorization code requests with an authorization server', function() {
it('sets authorize url for authorization code requests with an authorization server', function() {
oauthUtil.setupRedirect({
oktaAuthArgs: {
issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7',
Expand All @@ -1314,6 +1321,7 @@ define(function(require) {
state: oauthUtil.mockedState,
nonce: oauthUtil.mockedNonce,
scopes: ['openid', 'email'],
clientId: 'NPSfOkH5eZrTy8PMDlvx',
urls: {
issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7',
authorizeUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize',
Expand Down Expand Up @@ -1341,8 +1349,7 @@ define(function(require) {
'scope=openid%20email'
});
});

it('sets authorize url for authorization code (as an array) requests, ' +
it('sets authorize url for authorization code (as an array) requests, ' +
'defaulting responseMode to query', function() {
oauthUtil.setupRedirect({
getWithRedirectArgs: {
Expand All @@ -1357,6 +1364,7 @@ define(function(require) {
state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
scopes: ['openid', 'email'],
clientId: 'NPSfOkH5eZrTy8PMDlvx',
urls: {
issuer: 'https://auth-js-test.okta.com',
authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize',
Expand Down Expand Up @@ -1400,6 +1408,7 @@ define(function(require) {
state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
scopes: ['openid', 'email'],
clientId: 'NPSfOkH5eZrTy8PMDlvx',
urls: {
issuer: 'https://auth-js-test.okta.com',
authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize',
Expand Down Expand Up @@ -1443,6 +1452,7 @@ define(function(require) {
state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
scopes: ['openid', 'email'],
clientId: 'NPSfOkH5eZrTy8PMDlvx',
urls: {
issuer: 'https://auth-js-test.okta.com',
authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize',
Expand Down Expand Up @@ -2185,11 +2195,18 @@ define(function(require) {
});

describe('token.verify', function() {
var validationParams = {
clientId: tokens.standardIdTokenParsed.clientId,
issuer: tokens.standardIdTokenParsed.issuer
};

it('verifies a valid idToken with nonce', function(done) {
var client = setupSync();
util.warpToUnixTime(1449699929);
oauthUtil.loadWellKnownAndKeysCache();
client.token.verify(tokens.standardIdTokenParsed, oauthUtil.mockedNonce)
var alteredParams = _.clone(validationParams);
alteredParams.nonce = tokens.standardIdTokenParsed.nonce;
client.token.verify(tokens.standardIdTokenParsed, validationParams)
.then(function(res) {
expect(res).toEqual(tokens.standardIdTokenParsed);
})
Expand All @@ -2202,7 +2219,7 @@ define(function(require) {
var client = setupSync();
util.warpToUnixTime(1449699929);
oauthUtil.loadWellKnownAndKeysCache();
client.token.verify(tokens.standardIdTokenParsed)
client.token.verify(tokens.standardIdTokenParsed, validationParams)
.then(function(res) {
expect(res).toEqual(tokens.standardIdTokenParsed);
})
Expand Down Expand Up @@ -2232,37 +2249,39 @@ define(function(require) {
});
it('issued in the future', function(done) {
util.warpToDistantPast();
expectError([tokens.standardIdTokenParsed],
expectError([tokens.standardIdTokenParsed, validationParams],
'The JWT was issued in the future')
.fin(done);
});
it('expired', function(done) {
util.warpToDistantFuture();
expectError([tokens.standardIdTokenParsed],
expectError([tokens.standardIdTokenParsed, validationParams],
'The JWT expired and is no longer valid')
.fin(done);
});
it('invalid nonce', function(done) {
expectError([tokens.standardIdTokenParsed, 'invalidNonce'],
var alteredParams = _.clone(validationParams);
alteredParams.nonce = 'invalidNonce';
expectError([tokens.standardIdToken2Parsed, alteredParams],
'OAuth flow response nonce doesn\'t match request nonce')
.fin(done);
});
it('invalid audience', function(done) {
var idToken = _.clone(tokens.standardIdTokenParsed);
idToken.clientId = 'invalidAudience';
expectError([idToken],
var alteredParams = _.clone(validationParams);
alteredParams.clientId = 'invalidAudience';
expectError([tokens.standardIdTokenParsed, alteredParams],
'The audience [NPSfOkH5eZrTy8PMDlvx] does not match [invalidAudience]')
.fin(done);
});
it('invalid issuer', function(done) {
var idToken = _.clone(tokens.standardIdTokenParsed);
idToken.issuer = 'http://invalidissuer.example.com';
expectError([idToken],
var alteredParams = _.clone(validationParams);
alteredParams.issuer = 'http://invalidissuer.example.com';
expectError([tokens.standardIdTokenParsed, alteredParams],
'The issuer [https://auth-js-test.okta.com] does not match [http://invalidissuer.example.com]')
.fin(done);
});
it('expired before issued', function(done) {
expectError([tokens.expiredBeforeIssuedIdTokenParsed],
expectError([tokens.expiredBeforeIssuedIdTokenParsed, validationParams],
'The JWT expired before it was issued')
.fin(done);
});
Expand Down
12 changes: 12 additions & 0 deletions test/util/oauthUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ define(function(require) {

util.warpToUnixTime(getTime(opts.time));

// Mock the well-known and keys request
oauthUtil.loadWellKnownAndKeysCache();

if (opts.tokenManagerAddKeys) {
for (var key in opts.tokenManagerAddKeys) {
if (!opts.tokenManagerAddKeys.hasOwnProperty(key)) {
Expand Down Expand Up @@ -339,6 +342,9 @@ define(function(require) {
redirectUri: 'https://example.com/redirect'
});

// Mock the well-known and keys request
oauthUtil.loadWellKnownAndKeysCache();

oauthUtil.mockStateAndNonce();
var windowLocationMock = util.mockSetWindowLocation(client);
var setCookieMock = util.mockSetCookie();
Expand All @@ -361,6 +367,9 @@ define(function(require) {
redirectUri: 'https://example.com/redirect'
});

// Mock the well-known and keys request
oauthUtil.loadWellKnownAndKeysCache();

util.warpToUnixTime(getTime(opts.time));

// Mock location
Expand Down Expand Up @@ -429,6 +438,9 @@ define(function(require) {
redirectUri: 'https://example.com/redirect'
});

// Mock the well-known and keys request
oauthUtil.loadWellKnownAndKeysCache();

var emitter = new EventEmitter();
spyOn(window, 'addEventListener').and.callFake(function(eventName, fn) {
if (eventName === 'message') {
Expand Down

0 comments on commit 123b3e2

Please sign in to comment.