diff --git a/lib/clientBuilder.js b/lib/clientBuilder.js index 7a5b7abfb..6065ed13f 100644 --- a/lib/clientBuilder.js +++ b/lib/clientBuilder.js @@ -81,7 +81,7 @@ function OktaAuthBuilder(args) { sdk.idToken = { authorize: util.deprecateWrap('Use token.getWithoutPrompt, token.getWithPopup, or token.getWithRedirect ' + 'instead of idToken.authorize.', util.bind(token.getToken, null, sdk)), - verify: util.bind(token.verifyIdToken, null, sdk), + verify: util.deprecateWrap('Use token.verify instead of idToken.verify', util.bind(token.verifyIdToken, null, sdk)), refresh: util.deprecateWrap('Use token.refresh instead of idToken.refresh', util.bind(token.refreshIdToken, null, sdk)), decode: util.deprecateWrap('Use token.decode instead of idToken.decode', token.decodeToken) @@ -99,7 +99,8 @@ function OktaAuthBuilder(args) { parseFromUrl: util.bind(token.parseFromUrl, null, sdk), decode: token.decodeToken, refresh: util.bind(token.refreshToken, null, sdk), - getUserInfo: util.bind(token.getUserInfo, null, sdk) + getUserInfo: util.bind(token.getUserInfo, null, sdk), + verify: util.bind(token.verifyToken, null, sdk) }; // This is exposed so we can set window.location in our tests diff --git a/lib/crypto.js b/lib/crypto.js index ba131cd7f..70ed2f89a 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -13,6 +13,8 @@ var util = require('./util'); function verifyToken(idToken, key) { + key = util.clone(key); + var format = 'jwk'; var algo = { name: 'RSASSA-PKCS1-v1_5', @@ -21,6 +23,11 @@ function verifyToken(idToken, key) { var extractable = true; var usages = ['verify']; + // https://connect.microsoft.com/IE/feedback/details/2242108/webcryptoapi-importing-jwk-with-use-field-fails + // This is a metadata tag that specifies the intent of how the key should be used. + // It's not necessary to properly verify the jwt's signature. + delete key.use; + return crypto.subtle.importKey( format, key, diff --git a/lib/oauthUtil.js b/lib/oauthUtil.js index ac970c42c..efafb1ff8 100644 --- a/lib/oauthUtil.js +++ b/lib/oauthUtil.js @@ -2,6 +2,10 @@ var http = require('./http'); var util = require('./util'); var AuthSdkError = require('./errors/AuthSdkError'); +var config = require('./config'); +var storageBuilder = require('./storageBuilder'); + +var httpCache = storageBuilder(localStorage, config.CACHE_STORAGE_NAME); function isToken(obj) { if (obj && @@ -43,18 +47,60 @@ function loadPopup(src, options) { return window.open(src, title, appearance); } -function getWellKnown(sdk) { - // TODO: Use the issuer when known (usually from the id_token) - return http.get(sdk, sdk.options.url + '/.well-known/openid-configuration', { +function getWellKnown(sdk, issuer) { + return http.get(sdk, (issuer || sdk.options.url) + '/.well-known/openid-configuration', { cacheResponse: true }); } -function validateClaims(sdk, claims, aud, iss) { +function getKey(sdk, issuer, kid) { + return getWellKnown(sdk, issuer) + .then(function(wellKnown) { + var jwksUri = wellKnown['jwks_uri']; + + // Check our kid against the cached version (if it exists and isn't expired) + var cacheContents = httpCache.getStorage(); + var cachedResponse = cacheContents[jwksUri]; + if (cachedResponse && Date.now()/1000 < cachedResponse.expiresAt) { + var cachedKey = util.find(cachedResponse.response.keys, { + kid: kid + }); + + if (cachedKey) { + return cachedKey; + } + } + + // Remove cache for the key + httpCache.clearStorage(jwksUri); + + // Pull the latest keys if the key wasn't in the cache + return http.get(sdk, jwksUri, { + cacheResponse: true + }) + .then(function(res) { + var key = util.find(res.keys, { + kid: kid + }); + + if (key) { + return key; + } + + throw new AuthSdkError('The key id, ' + kid + ', was not found in the server\'s keys'); + }); + }); +} + +function validateClaims(sdk, claims, aud, iss, nonce) { if (!claims || !iss || !aud) { throw new AuthSdkError('The jwt, iss, and aud arguments are all required'); } + if (nonce && claims.nonce !== nonce) { + throw new AuthSdkError('OAuth flow response nonce doesn\'t match request nonce'); + } + var now = Math.floor(new Date().getTime()/1000); if (claims.iss !== iss) { @@ -180,6 +226,7 @@ function hashToObject(hash) { module.exports = { getWellKnown: getWellKnown, + getKey: getKey, validateClaims: validateClaims, getOAuthUrls: getOAuthUrls, loadFrame: loadFrame, diff --git a/lib/storageBuilder.js b/lib/storageBuilder.js index fe5c85368..220cfa434 100644 --- a/lib/storageBuilder.js +++ b/lib/storageBuilder.js @@ -21,8 +21,13 @@ function storageBuilder(webstorage, storageName) { } } - function clearStorage() { - setStorage({}); + function clearStorage(key) { + if (!key) { + setStorage({}); + } + var storage = getStorage(); + delete storage[key]; + setStorage(storage); } function updateStorage(key, value) { diff --git a/lib/token.js b/lib/token.js index 0d08d84dd..7d4b62824 100644 --- a/lib/token.js +++ b/lib/token.js @@ -92,6 +92,37 @@ function verifyIdToken(sdk, idToken, options) { }); } +function verifyToken(sdk, token, nonce, ignoreSignature) { + return new Q() + .then(function() { + if (!token || !token.idToken) { + throw new AuthSdkError('Only idTokens may be verified'); + } + + var jwt = decodeToken(token.idToken); + + // Standard claim validation + oauthUtil.validateClaims(sdk, jwt.payload, token.clientId, token.issuer, nonce); + + // If the browser doesn't support native crypto or we choose not + // to verify the signature, bail early + if (ignoreSignature || !sdk.features.isTokenVerifySupported()) { + return token; + } + + return oauthUtil.getKey(sdk, token.issuer, jwt.header.kid) + .then(function(key) { + return sdkCrypto.verifyToken(token.idToken, key); + }) + .then(function(valid) { + if (!valid) { + throw new AuthSdkError('The token signature is not valid'); + } + return token; + }); + }); +} + function refreshIdToken(sdk, options) { options = options || {}; options.display = null; @@ -151,87 +182,75 @@ function addFragmentListener(sdk, windowEl, timeout) { function handleOAuthResponse(sdk, oauthParams, res, urls) { urls = urls || {}; - if (res['error'] || res['error_description']) { - throw new OAuthError(res['error'], res['error_description']); - } - - if (res.state !== oauthParams.state) { - throw new AuthSdkError('OAuth flow response state doesn\'t match request state'); - } - var tokenTypes = oauthParams.responseType; var scopes = util.clone(oauthParams.scopes); - var tokenDict = {}; + var clientId = oauthParams.clientId || sdk.options.clientId; - if (res['id_token']) { - var jwt = sdk.token.decode(res['id_token']); - if (jwt.payload.nonce !== oauthParams.nonce) { - throw new AuthSdkError('OAuth flow response nonce doesn\'t match request nonce'); + return new Q() + .then(function() { + if (res['error'] || res['error_description']) { + throw new OAuthError(res['error'], res['error_description']); } - var clientId = oauthParams.clientId || sdk.options.clientId; - oauthUtil.validateClaims(sdk, jwt.payload, clientId, urls.issuer); - - var idToken = { - idToken: res['id_token'], - claims: jwt.payload, - expiresAt: jwt.payload.exp, - scopes: scopes, - authorizeUrl: urls.authorizeUrl, - issuer: urls.issuer - }; + if (res.state !== oauthParams.state) { + throw new AuthSdkError('OAuth flow response state doesn\'t match request state'); + } - if (Array.isArray(tokenTypes)) { - tokenDict['id_token'] = idToken; - } else { - return idToken; + var tokenDict = {}; + + if (res['access_token']) { + tokenDict['token'] = { + accessToken: res['access_token'], + expiresAt: Number(res['expires_in']) + Math.floor(Date.now()/1000), + tokenType: res['token_type'], + scopes: scopes, + authorizeUrl: urls.authorizeUrl, + userinfoUrl: urls.userinfoUrl + }; } - } - - if (res['access_token']) { - var accessToken = { - accessToken: res['access_token'], - expiresAt: Number(res['expires_in']) + Math.floor(Date.now()/1000), - tokenType: res['token_type'], - scopes: scopes, - authorizeUrl: urls.authorizeUrl, - userinfoUrl: urls.userinfoUrl - }; - if (Array.isArray(tokenTypes)) { - tokenDict['token'] = accessToken; - } else { - return accessToken; + if (res['code']) { + tokenDict['code'] = { + authorizationCode: res['code'] + }; } - } - if (res['code']) { - var authorizationCode = { - authorizationCode: res['code'] - }; + if (res['id_token']) { + var jwt = sdk.token.decode(res['id_token']); + + var idToken = { + idToken: res['id_token'], + claims: jwt.payload, + expiresAt: jwt.payload.exp, + scopes: scopes, + authorizeUrl: urls.authorizeUrl, + issuer: urls.issuer, + clientId: clientId + }; - if (Array.isArray(tokenTypes)) { - tokenDict['code'] = authorizationCode; - } else { - return authorizationCode; + return verifyToken(sdk, idToken, oauthParams.nonce, true) + .then(function(token) { + tokenDict['id_token'] = idToken; + return tokenDict; + }); } - } - if (!tokenDict['token'] && !tokenDict['id_token']) { - throw new AuthSdkError('Unable to parse OAuth flow response'); - } - - var tokens = []; + return tokenDict; + }) + .then(function(tokenDict) { + if (!Array.isArray(tokenTypes)) { + return tokenDict[tokenTypes]; + } - // Create token array in the order of the responseType array - for (var t = 0, tl = tokenTypes.length; t < tl; t++) { - var tokenType = tokenTypes[t]; - if (tokenDict[tokenType]) { - tokens.push(tokenDict[tokenType]); + if (!tokenDict['token'] && !tokenDict['id_token']) { + throw new AuthSdkError('Unable to parse OAuth flow response'); } - } - return tokens; + // Create token array in the order of the responseType array + return tokenTypes.map(function(item) { + return tokenDict[item]; + }); + }); } function getDefaultOAuthParams(sdk, oauthOptions) { @@ -625,5 +644,6 @@ module.exports = { decodeToken: decodeToken, verifyIdToken: verifyIdToken, refreshToken: refreshToken, - getUserInfo: getUserInfo + getUserInfo: getUserInfo, + verifyToken: verifyToken }; diff --git a/test/spec/general.js b/test/spec/general.js index c258b41ce..1fd134122 100644 --- a/test/spec/general.js +++ b/test/spec/general.js @@ -87,7 +87,7 @@ define(function(require) { setupVerifyIdTokenTest({ title: 'verifies a valid idToken', - idToken: tokens.verifiableIdToken, + idToken: tokens.standardIdToken, expectations: function (test, res) { expect(res).toEqual(true); } @@ -120,7 +120,7 @@ define(function(require) { setupVerifyIdTokenTest({ title: 'rejects an invalid idToken due to expiration', - idToken: tokens.verifiableIdToken, + idToken: tokens.standardIdToken, execute: function(test, opts) { util.warpToDistantPast(); return test.oa.idToken.verify(opts.idToken, opts.verifyOpts) @@ -137,7 +137,7 @@ define(function(require) { setupVerifyIdTokenTest({ title: 'verifies an idToken that would be invalid, except ' + 'we\'re using the expirationTime option', - idToken: tokens.verifiableIdToken, + idToken: tokens.standardIdToken, verifyOpts: { expirationTime: 9999999999 }, @@ -156,7 +156,7 @@ define(function(require) { setupVerifyIdTokenTest({ title: 'verifies a valid idToken using single audience option', - idToken: tokens.verifiableIdToken, + idToken: tokens.standardIdToken, verifyOpts: { audience: 'NPSfOkH5eZrTy8PMDlvx' }, @@ -167,7 +167,7 @@ define(function(require) { setupVerifyIdTokenTest({ title: 'rejects an invalid idToken using single audience option', - idToken: tokens.verifiableIdToken, + idToken: tokens.standardIdToken, verifyOpts: { audience: 'invalid' }, @@ -178,7 +178,7 @@ define(function(require) { setupVerifyIdTokenTest({ title: 'verifies a valid idToken using multiple audience option (all valid)', - idToken: tokens.verifiableIdToken, + idToken: tokens.standardIdToken, verifyOpts: { audience: ['NPSfOkH5eZrTy8PMDlvx', 'NPSfOkH5eZrTy8PMDlvx'] }, @@ -189,7 +189,7 @@ define(function(require) { setupVerifyIdTokenTest({ title: 'verifies a valid idToken using multiple audience option (valid and invalid)', - idToken: tokens.verifiableIdToken, + idToken: tokens.standardIdToken, verifyOpts: { audience: ['NPSfOkH5eZrTy8PMDlvx', 'invalid2'] }, @@ -200,7 +200,7 @@ define(function(require) { setupVerifyIdTokenTest({ title: 'rejects an invalid idToken using multiple audience option (all invalid)', - idToken: tokens.verifiableIdToken, + idToken: tokens.standardIdToken, verifyOpts: { audience: ['invalid1', 'invalid2'] }, @@ -211,9 +211,9 @@ define(function(require) { setupVerifyIdTokenTest({ title: 'verifies a valid idToken using issuer option', - idToken: tokens.verifiableIdToken, + idToken: tokens.standardIdToken, verifyOpts: { - issuer: 'https://lboyette.trexcloud.com' + issuer: 'https://auth-js-test.okta.com' }, expectations: function (test, res) { expect(res).toEqual(true); @@ -222,7 +222,7 @@ define(function(require) { setupVerifyIdTokenTest({ title: 'rejects an invalid idToken using issuer option', - idToken: tokens.verifiableIdToken, + idToken: tokens.standardIdToken, verifyOpts: { issuer: 'invalid' }, diff --git a/test/spec/oauth.js b/test/spec/oauth.js index 6f9859b24..c9ebed10f 100644 --- a/test/spec/oauth.js +++ b/test/spec/oauth.js @@ -437,7 +437,7 @@ define(function(require) { sessionToken: 'testToken' }, postMessageResp: { - 'id_token': tokens.expiredBeforeIssuedToken, + 'id_token': tokens.expiredBeforeIssuedIdToken, state: oauthUtil.mockedState } }, diff --git a/test/spec/oauthUtil.js b/test/spec/oauthUtil.js index a8a843e79..7d6a300e6 100644 --- a/test/spec/oauthUtil.js +++ b/test/spec/oauthUtil.js @@ -1,8 +1,11 @@ define(function(require) { var OktaAuth = require('OktaAuth'); var oauthUtil = require('../../lib/oauthUtil'); + var oauthUtilHelpers = require('../util/oauthUtil'); var util = require('../util/util'); var wellKnown = require('../xhr/well-known'); + var keys = require('../xhr/keys'); + var tokens = require('../util/tokens'); describe('getWellKnown', function() { util.itMakesCorrectRequestResponse({ @@ -95,6 +98,154 @@ define(function(require) { }); }); + describe('getKey', function() { + util.itMakesCorrectRequestResponse({ + title: 'uses existing jwks on valid kid', + setup: { + time: 1449699929 + }, + execute: function(test) { + oauthUtilHelpers.loadWellKnownAndKeysCache(); + return oauthUtil.getKey(test.oa, test.oa.options.url, 'U5R8cHbGw445Qbq8zVO1PcCpXL8yG6IcovVa3laCoxM'); + }, + expectations: function(test, key) { + expect(key).toEqual(tokens.standardKey); + } + }); + util.itMakesCorrectRequestResponse({ + title: 'pulls new jwks on valid kid', + setup: { + calls: [ + { + request: { + method: 'get', + uri: '/oauth2/v1/keys' + }, + response: 'keys' + } + ], + time: 1449699929 + }, + execute: function(test) { + oauthUtilHelpers.loadWellKnownCache(); + return oauthUtil.getKey(test.oa, test.oa.options.url, 'U5R8cHbGw445Qbq8zVO1PcCpXL8yG6IcovVa3laCoxM'); + }, + expectations: function(test, key) { + expect(key).toEqual(tokens.standardKey); + var cache = localStorage.getItem('okta-cache-storage'); + expect(cache).toEqual(JSON.stringify({ + 'https://auth-js-test.okta.com/.well-known/openid-configuration': { + expiresAt: 1449786329, + response: wellKnown.response + }, + 'https://auth-js-test.okta.com/oauth2/v1/keys': { + expiresAt: 1449786329, + response: keys.response + } + })); + } + }); + + util.itMakesCorrectRequestResponse({ + title: 'checks existing jwks then pulls new jwks on valid kid', + setup: { + calls: [ + { + request: { + method: 'get', + uri: '/oauth2/v1/keys' + }, + response: 'keys' + } + ], + time: 1449699929 + }, + execute: function(test) { + // Put a modified kid in the cache + localStorage.setItem('okta-cache-storage', JSON.stringify({ + 'https://auth-js-test.okta.com/.well-known/openid-configuration': { + expiresAt: 1449786329, + response: wellKnown.response + }, + 'https://auth-js-test.okta.com/oauth2/v1/keys': { + expiresAt: 1449786329, + response: { + 'keys': [{ + alg: 'RS256', + kty: 'RSA', + n: 'fake', + e: 'AQAB', + use: 'sig', + kid: 'modifiedKeyId' + }] + } + } + })); + + return oauthUtil.getKey(test.oa, test.oa.options.url, 'U5R8cHbGw445Qbq8zVO1PcCpXL8yG6IcovVa3laCoxM'); + }, + expectations: function(test, key) { + expect(key).toEqual(tokens.standardKey); + var cache = localStorage.getItem('okta-cache-storage'); + expect(cache).toEqual(JSON.stringify({ + 'https://auth-js-test.okta.com/.well-known/openid-configuration': { + expiresAt: 1449786329, + response: wellKnown.response + }, + 'https://auth-js-test.okta.com/oauth2/v1/keys': { + expiresAt: 1449786329, + response: keys.response + } + })); + } + }); + + util.itErrorsCorrectly({ + title: 'checks existing jwks then pulls new jwks on invalid kid', + setup: { + calls: [ + { + request: { + method: 'get', + uri: '/oauth2/v1/keys' + }, + response: 'keys' + } + ], + time: 1449699929 + }, + execute: function(test) { + // Put a modified kid in the cache + localStorage.setItem('okta-cache-storage', JSON.stringify({ + 'https://auth-js-test.okta.com/.well-known/openid-configuration': { + expiresAt: 1449786329, + response: wellKnown.response + }, + 'https://auth-js-test.okta.com/oauth2/v1/keys': { + expiresAt: 1449786329, + response: keys.response + } + })); + + return oauthUtil.getKey(test.oa, test.oa.options.url, 'invalidKid'); + }, + expectations: function(test, err) { + util.assertAuthSdkError(err, 'The key id, invalidKid, was not found in the server\'s keys'); + var cache = localStorage.getItem('okta-cache-storage'); + expect(cache).toEqual(JSON.stringify({ + 'https://auth-js-test.okta.com/.well-known/openid-configuration': { + expiresAt: 1449786329, + response: wellKnown.response + }, + 'https://auth-js-test.okta.com/oauth2/v1/keys': { + expiresAt: 1449786329, + response: keys.response + } + })); + } + }); + }); + describe('getOAuthUrls', function() { function setupOAuthUrls(options) { var sdk = new OktaAuth(options.oktaAuthArgs || { diff --git a/test/spec/token.js b/test/spec/token.js index 0805b66a7..50773a062 100644 --- a/test/spec/token.js +++ b/test/spec/token.js @@ -4,6 +4,7 @@ define(function(require) { var util = require('../util/util'); var oauthUtil = require('../util/oauthUtil'); var packageJson = require('../../package.json'); + var _ = require('lodash'); var Q = require('q'); function setupSync() { @@ -1558,6 +1559,32 @@ define(function(require) { }); }); + it('parses access_token, code, and id_token, but one isn\'t returned', function(done) { + return oauthUtil.setupParseUrl({ + time: 1449699929, + hashMock: '#access_token=' + tokens.standardAccessToken + + '&id_token=' + tokens.standardIdToken + + '&expires_in=3600' + + '&token_type=Bearer' + + '&state=' + oauthUtil.mockedState, + oauthCookie: 'okta-oauth-redirect-params=' + JSON.stringify({ + responseType: ['id_token', 'code', 'token'], + state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + scopes: ['openid', 'email'], + urls: { + issuer: 'https://auth-js-test.okta.com', + authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', + userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' + } + }) + '; path=/;', + expectedResp: [tokens.standardIdTokenParsed, undefined, tokens.standardAccessTokenParsed] + }) + .fin(function() { + done(); + }); + }); + it('parses access_token, id_token, and code with authorization server issuer', function(done) { return oauthUtil.setupParseUrl({ time: 1449699929, @@ -1785,6 +1812,20 @@ define(function(require) { 'scope': 'openid email', 'prompt': 'none' } + }, + time: 1449699929, + postMessageResp: { + 'access_token': tokens.standardAccessToken, + 'token_type': 'Bearer', + 'expires_in': 3600, + 'state': oauthUtil.mockedState + }, + expectedResp: { + accessToken: tokens.standardAccessToken, + expiresAt: 1449703529, + scopes: ['openid', 'email'], + tokenType: 'Bearer', + issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7' } }) .fin(function() { @@ -1994,4 +2035,89 @@ define(function(require) { } }); }); + + describe('token.verify', function() { + it('verifies a valid idToken with nonce', function(done) { + var client = setupSync(); + util.warpToUnixTime(1449699929); + oauthUtil.loadWellKnownAndKeysCache(); + client.token.verify(tokens.standardIdTokenParsed, oauthUtil.mockedNonce) + .then(function(res) { + expect(res).toEqual(tokens.standardIdTokenParsed); + }) + .fail(function() { + expect('not to be hit').toEqual(true); + }) + .fin(done); + }); + it('verifies a valid idToken without nonce', function(done) { + var client = setupSync(); + util.warpToUnixTime(1449699929); + oauthUtil.loadWellKnownAndKeysCache(); + client.token.verify(tokens.standardIdTokenParsed) + .then(function(res) { + expect(res).toEqual(tokens.standardIdTokenParsed); + }) + .fail(function() { + expect('not to be hit').toEqual(true); + }) + .fin(done); + }); + + describe('rejects a token', function() { + function expectError(verifyArgs, message, done) { + var client = setupSync(); + return client.token.verify.apply(null, verifyArgs) + .then(function(res) { + expect('not to be hit').toEqual(true); + }) + .fail(function(err) { + util.assertAuthSdkError(err, message); + }) + .fin(done); + } + + it('isn\'t an idToken', function(done) { + expectError([tokens.standardAccessTokenParsed], + 'Only idTokens may be verified') + .fin(done); + }); + it('issued in the future', function(done) { + util.warpToDistantPast(); + expectError([tokens.standardIdTokenParsed], + 'The JWT was issued in the future') + .fin(done); + }); + it('expired', function(done) { + util.warpToDistantFuture(); + expectError([tokens.standardIdTokenParsed], + 'The JWT expired and is no longer valid') + .fin(done); + }); + it('invalid nonce', function(done) { + expectError([tokens.standardIdTokenParsed, 'invalidNonce'], + '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], + '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], + '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], + 'The JWT expired before it was issued') + .fin(done); + }); + }); + }); }); diff --git a/test/spec/tokenManager.js b/test/spec/tokenManager.js index 25beab3c6..b22f987c7 100644 --- a/test/spec/tokenManager.js +++ b/test/spec/tokenManager.js @@ -12,7 +12,7 @@ define(function(require) { redirectUri: 'https://example.com/redirect', tokenManager: { storage: options.type, - autoRefresh: options.autoRefresh + autoRefresh: options.autoRefresh || false } }); } @@ -64,7 +64,10 @@ define(function(require) { oktaAuthArgs: { url: 'https://auth-js-test.okta.com', clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect' + redirectUri: 'https://example.com/redirect', + tokenManager: { + autoRefresh: false + } }, tokenManagerAddKeys: { 'test-idToken': { @@ -107,7 +110,10 @@ define(function(require) { oktaAuthArgs: { url: 'https://auth-js-test.okta.com', clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect' + redirectUri: 'https://example.com/redirect', + tokenManager: { + autoRefresh: false + } }, tokenManagerAddKeys: { 'test-accessToken': { diff --git a/test/util/oauthUtil.js b/test/util/oauthUtil.js index f3ab2ecdc..c37332823 100644 --- a/test/util/oauthUtil.js +++ b/test/util/oauthUtil.js @@ -6,6 +6,9 @@ define(function(require) { var Q = require('q'); var EventEmitter = require('tiny-emitter'); var _ = require('lodash'); + var wellKnown = require('../xhr/well-known'); + var wellKnownSharedResource = require('../xhr/well-known-shared-resource'); + var keys = require('../xhr/keys'); var oauthUtil = {}; @@ -20,7 +23,39 @@ define(function(require) { // Make sure the state is generated the same every time (standardState, standardNonce) spyOn(Math, 'random').and.callFake(function() { return 0; - }); + }); + }; + + oauthUtil.loadWellKnownCache = function() { + localStorage.setItem('okta-cache-storage', JSON.stringify({ + 'https://auth-js-test.okta.com/.well-known/openid-configuration': { + expiresAt: 1449786329, + response: wellKnown.response + } + })); + }; + + oauthUtil.loadWellKnownAndKeysCache = function() { + // add /.well-known/openid-configuration and /oauth2/v1/keys to cache + // so we don't make unnecessary requests + localStorage.setItem('okta-cache-storage', JSON.stringify({ + 'https://auth-js-test.okta.com/.well-known/openid-configuration': { + expiresAt: 1449786329, + response: wellKnown.response + }, + 'https://auth-js-test.okta.com/oauth2/v1/keys': { + expiresAt: 1449786329, + response: keys.response + }, + 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/.well-known/openid-configuration': { + expiresAt: 1449786329, + response: wellKnownSharedResource.response + }, + 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/keys': { + expiresAt: 1449786329, + response: keys.response + } + })); }; var defaultPostMessage = { @@ -45,6 +80,10 @@ define(function(require) { function validateResponse(res, expectedResp) { function expectResponsesToEqual(actual, expected) { + if (!actual || !expected) { + expect(actual, expected); + return; + } expect(actual.idToken).toEqual(expected.idToken); expect(actual.claims).toEqual(expected.claims); expect(actual.accessToken).toEqual(expected.accessToken); diff --git a/test/util/tokens.js b/test/util/tokens.js index 175b7c368..73e3efb1b 100644 --- a/test/util/tokens.js +++ b/test/util/tokens.js @@ -1,4 +1,4 @@ -/* eslint max-statements:[2,22] */ +/* eslint max-statements:[2,24] */ define(function() { var tokens = {}; @@ -25,43 +25,15 @@ define(function() { signature: 'TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ' }; - tokens.verifiableIdToken = 'eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIwMHUxcGNsYTVxWUlSRU' + - 'RMV0NRViIsIm5hbWUiOiJMZW4gQm95ZXR0ZSIsImdpdmVuX25hb' + - 'WUiOiJMZW4iLCJmYW1pbHlfbmFtZSI6IkJveWV0dGUiLCJ1cGRh' + - 'dGVkX2F0IjoxNDQ2MTUzNDAxLCJlbWFpbCI6Imxib3lldHRlQG9' + - 'rdGEuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInZlciI6MS' + - 'wiaXNzIjoiaHR0cHM6Ly9sYm95ZXR0ZS50cmV4Y2xvdWQuY29tI' + - 'iwibG9naW4iOiJhZG1pbkBva3RhLmNvbSIsImF1ZCI6Ik5QU2ZP' + - 'a0g1ZVpyVHk4UE1EbHZ4IiwiaWF0IjoxNDQ5Njk2MzMwLCJleHA' + - 'iOjE0NDk2OTk5MzAsImFtciI6WyJrYmEiLCJtZmEiLCJwd2QiXS' + - 'wianRpIjoiVFJaVDdSQ2lTeW1UczVXN1J5aDMiLCJhdXRoX3Rpb' + - 'WUiOjE0NDk2OTYzMzB9.YWCNE3ZvT-8ceKnAbTkmSxYE-jIPpfh' + - '2s8f_hTagUUxrfdKgyWzBb9iN3GOPaQ2K6jqOFx90RI2GBzAWec' + - 'pel3sAxG-wvLqiy0d8g0CUb7XTHdhXOLRrXvlpbULxdNnMbBcc6' + - 'uOLDalBjrumOiDMLzti-Bx6uQQ0EjUwuC-Dhv7I3wMsVxyEKejv' + - 'jMLbfWJ6iu4-UUx1r8_ZZUjDDXSB3OFXJQ3nPwRVFXZuRNhGScL' + - 'nftXz7mypRGxrapIQusym1K8hk9uy8_KYL2H2QNbyIqK9Vh9JhY' + - '1rtkQNpv3ZerCUXEVGRiEXDqR_OHu4vUi1-FkONZZe2ov8dQ1mX' + - 'iHHdw'; - - tokens.standardIdToken = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOi' + - 'IwMHUxcGNsYTVxWUlSRURMV0NRViIsIm5hbWUiOiJTYW1sI' + - 'EphY2tzb24iLCJnaXZlbl9uYW1lIjoiU2FtbCIsImZhbWls' + - 'eV9uYW1lIjoiSmFja3NvbiIsInVwZGF0ZWRfYXQiOjE0NDY' + - 'xNTM0MDEsImVtYWlsIjoic2FtbGphY2tzb25Ab2t0YS5jb2' + - '0iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwidmVyIjoxLCJpc' + - '3MiOiJodHRwczovL2F1dGgtanMtdGVzdC5va3RhLmNvbSIs' + - 'ImxvZ2luIjoiYWRtaW5Ab2t0YS5jb20iLCJub25jZSI6ImF' + - 'hYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYW' + - 'FhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWEiLCJhdWQiO' + - 'iJOUFNmT2tINWVaclR5OFBNRGx2eCIsImlhdCI6MTQ0OTY5' + - 'NjMzMCwiZXhwIjoxNDQ5Njk5OTMwLCJhbXIiOlsia2JhIiw' + - 'ibWZhIiwicHdkIl0sImp0aSI6IlRSWlQ3UkNpU3ltVHM1Vz' + - 'dSeWgzIiwiYXV0aF90aW1lIjoxNDQ5Njk2MzMwfQ.PDuo7r' + - 'brqHIqXRIuF5eP-hovEs9ZBVuwXf7_qKUKld-2c7YVguuSO' + - 'uhXvC4ngcZhxjw9Y0nefogFdI47Qqhdw-dggtgsGzHxiPvr' + - 't0e5Vh4m5L4lVedSpsCdlMIPOv78-d_N6sAAXPiQ3MYhu5x' + - 'zhMm8Y_PK8JZnFtfN47vrNlk'; + tokens.standardIdToken = 'eyJhbGciOiJSUzI1NiIsImtpZCI6IlU1UjhjSGJHdzQ0NVFicTh6Vk8xUGNDcFhMOHlHNkljb3ZWYTNsYUNveE0i' + + 'fQ.eyJzdWIiOiIwMHUxcGNsYTVxWUlSRURMV0NRViIsIm5hbWUiOiJTYW1sIEphY2tzb24iLCJnaXZlbl9uYW1lIjoiU2FtbCIsImZhbWlseV9u' + + 'YW1lIjoiSmFja3NvbiIsInVwZGF0ZWRfYXQiOjE0NDYxNTM0MDEsImVtYWlsIjoic2FtbGphY2tzb25Ab2t0YS5jb20iLCJlbWFpbF92ZXJpZml' + + 'lZCI6dHJ1ZSwidmVyIjoxLCJpc3MiOiJodHRwczovL2F1dGgtanMtdGVzdC5va3RhLmNvbSIsImxvZ2luIjoiYWRtaW5Ab2t0YS5jb20iLCJub2' + + '5jZSI6ImFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWEiLCJhdWQiOiJOUFNmT' + + '2tINWVaclR5OFBNRGx2eCIsImlhdCI6MTQ0OTY5NjMzMCwiZXhwIjoxNDQ5Njk5OTMwLCJhbXIiOlsia2JhIiwibWZhIiwicHdkIl0sImp0aSI6' + + 'IlRSWlQ3UkNpU3ltVHM1VzdSeWgzIiwiYXV0aF90aW1lIjoxNDQ5Njk2MzMwfQ.tdspicRE-0IrFKwjCT2Uo2gExQyTAftcp4cuA3iIF6_uYiqQ' + + '9Q4SZHCjMbuWdXrUSM-_UkDpD6sbG_ZRcdZQJ7geeIEjKpV4x792iiP_f1H-HLbAMIDWynp5FR4QQO1Q4ndNOwIsrUqf06vYazz9ildQde2uOTw' + + 'caUCsz2M0lSU'; tokens.standardIdTokenClaims = { 'sub': '00u1pcla5qYIREDLWCQV', @@ -93,28 +65,20 @@ define(function() { expiresAt: 1449699930, scopes: ['openid', 'email'], authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', - issuer: 'https://auth-js-test.okta.com' + issuer: 'https://auth-js-test.okta.com', + clientId: 'NPSfOkH5eZrTy8PMDlvx' }; // Uses modified nonce for testing simultaneous iframes - tokens.standardIdToken2 = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOi' + - 'IwMHUxcGNsYTVxWUlSRURMV0NRViIsIm5hbWUiOiJTYW1sI' + - 'EphY2tzb24iLCJnaXZlbl9uYW1lIjoiU2FtbCIsImZhbWls' + - 'eV9uYW1lIjoiSmFja3NvbiIsInVwZGF0ZWRfYXQiOjE0NDY' + - 'xNTM0MDEsImVtYWlsIjoic2FtbGphY2tzb25Ab2t0YS5jb2' + - '0iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwidmVyIjoxLCJpc' + - '3MiOiJodHRwczovL2F1dGgtanMtdGVzdC5va3RhLmNvbSIs' + - 'ImxvZ2luIjoiYWRtaW5Ab2t0YS5jb20iLCJub25jZSI6ImJ' + - 'iYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYm' + - 'JiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmIiLCJhdWQiO' + - 'iJOUFNmT2tINWVaclR5OFBNRGx2eCIsImlhdCI6MTQ0OTY5' + - 'NjMzMCwiZXhwIjoxNDQ5Njk5OTMwLCJhbXIiOlsia2JhIiw' + - 'ibWZhIiwicHdkIl0sImp0aSI6IlRSWlQ3UkNpU3ltVHM1Vz' + - 'dSeWgzIiwiYXV0aF90aW1lIjoxNDQ5Njk2MzMwfQ.xiKZc7' + - 'K5Ly0tcouyVfWeAU9DXuyGYRVA_VxYBnigP9rdgCvzA_QxL' + - '74uJKxCDI1eDFhDisocDTuQR9i924-v8CO1GmaoWco--vgW' + - 'pYcGEgLFRDRhfVTsnocExDcSbQ8XFt7PBrDJVtzMxgAX75O' + - '4MDMenm2VBKVmMWRcUKTJfyc'; + tokens.standardIdToken2 = 'eyJhbGciOiJSUzI1NiIsImtpZCI6IlU1UjhjSGJHdzQ0NVFicTh6Vk8xUGNDcFhMOHlHNkljb3ZWYTNsYUNveE0' + + 'ifQ.eyJzdWIiOiIwMHUxcGNsYTVxWUlSRURMV0NRViIsIm5hbWUiOiJTYW1sIEphY2tzb24iLCJnaXZlbl9uYW1lIjoiU2FtbCIsImZhbWlseV9' + + 'uYW1lIjoiSmFja3NvbiIsInVwZGF0ZWRfYXQiOjE0NDYxNTM0MDEsImVtYWlsIjoic2FtbGphY2tzb25Ab2t0YS5jb20iLCJlbWFpbF92ZXJpZm' + + 'llZCI6dHJ1ZSwidmVyIjoxLCJpc3MiOiJodHRwczovL2F1dGgtanMtdGVzdC5va3RhLmNvbSIsImxvZ2luIjoiYWRtaW5Ab2t0YS5jb20iLCJub' + + '25jZSI6ImJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmIiLCJhdWQiOiJOUFNm' + + 'T2tINWVaclR5OFBNRGx2eCIsImlhdCI6MTQ0OTY5NjMzMCwiZXhwIjoxNDQ5Njk5OTMwLCJhbXIiOlsia2JhIiwibWZhIiwicHdkIl0sImp0aSI' + + '6IlRSWlQ3UkNpU3ltVHM1VzdSeWgzIiwiYXV0aF90aW1lIjoxNDQ5Njk2MzMwfQ.XABmqTp0TiXKu-BuvZ6XgJj11LQxXQGcludepzm71zSB38E' + + '6Td69ztugF-SVrGk_iD_k4n-lpnyfnbQt_rGFuUmAn_PsXC8DogAziSVxE96AF6G7X9rpvhnFkdc4wmt8X71oHhDuwiuAh7BrXYdvkCLDEh4Hgw' + + 'Iu4M_1dJg2308'; tokens.standardIdToken2Claims = { 'sub': '00u1pcla5qYIREDLWCQV', @@ -146,26 +110,62 @@ define(function() { expiresAt: 1449699930, scopes: ['openid', 'email'], authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', - issuer: 'https://auth-js-test.okta.com' + issuer: 'https://auth-js-test.okta.com', + clientId: 'NPSfOkH5eZrTy8PMDlvx' + }; + + tokens.expiredBeforeIssuedIdToken = 'eyJhbGciOiJSUzI1NiIsImtpZCI6IlU1UjhjSGJHdzQ0NVFicTh6Vk8xUGNDcFhMOHlHNkljb3ZWY' + + 'TNsYUNveE0ifQ.eyJzdWIiOiIwMHUxcGNsYTVxWUlSRURMV0NRViIsIm5hbWUiOiJTYW1sIEphY2tzb24iLCJnaXZlbl9uYW1lIjoiU2FtbCIsI' + + 'mZhbWlseV9uYW1lIjoiSmFja3NvbiIsInVwZGF0ZWRfYXQiOjE0NDYxNTM0MDEsImVtYWlsIjoic2FtbGphY2tzb25Ab2t0YS5jb20iLCJlbWFp' + + 'bF92ZXJpZmllZCI6dHJ1ZSwidmVyIjoxLCJpc3MiOiJodHRwczovL2F1dGgtanMtdGVzdC5va3RhLmNvbSIsImxvZ2luIjoiYWRtaW5Ab2t0YS5' + + 'jb20iLCJub25jZSI6ImFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWEiLCJhdW' + + 'QiOiJOUFNmT2tINWVaclR5OFBNRGx2eCIsImlhdCI6MTQ0OTY5NjMzMCwiZXhwIjoxNDQ5NjkwMDAwLCJhbXIiOlsia2JhIiwibWZhIiwicHdkI' + + 'l0sImp0aSI6IlRSWlQ3UkNpU3ltVHM1VzdSeWgzIiwiYXV0aF90aW1lIjoxNDQ5Njk2MzMwfQ.K6jaWgn2pX5bZx0MZBax6Y0JetCDIlJp2iUEY' + + 'PO1teGQGTGIC6qjcKlSyWVlWKTNYGJSHk24NmKa78Idxa4CaWQCaIxP_wvMJv0dQjb5nwVtyPz5X8ez46MYhkwArC2hEl9JVb2jE7ElOW2XvU5x' + + 'TaMRlXLsimDp3XNlnQ8aTiI'; + + tokens.expiredBeforeIssuedIdTokenClaims = { + 'sub': '00u1pcla5qYIREDLWCQV', + 'name': 'Saml Jackson', + 'given_name': 'Saml', + 'family_name': 'Jackson', + 'updated_at': 1446153401, + 'email': 'samljackson@okta.com', + 'email_verified': true, + 'ver': 1, + 'iss': 'https://auth-js-test.okta.com', + 'login': 'admin@okta.com', + 'nonce': 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + 'aud': 'NPSfOkH5eZrTy8PMDlvx', + 'iat': 1449696330, + 'exp': 1449690000, + 'amr': [ + 'kba', + 'mfa', + 'pwd' + ], + 'jti': 'TRZT7RCiSymTs5W7Ryh3', + 'auth_time': 1449696330 + }; + + tokens.expiredBeforeIssuedIdTokenParsed = { + idToken: tokens.expiredBeforeIssuedIdToken, + claims: tokens.expiredBeforeIssuedIdTokenClaims, + expiresAt: 1449690000, + scopes: ['openid', 'email'], + authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', + issuer: 'https://auth-js-test.okta.com', + clientId: 'NPSfOkH5eZrTy8PMDlvx' }; - tokens.authServerIdToken = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiO' + - 'iIwMHVrb2VFcUlvZ2lGSHBEZTBnMyIsImVtYWlsIjoic2F' + - 'tbGphY2tzb25Ab2t0YS5jb20iLCJ2ZXIiOjEsImlzcyI6I' + - 'mh0dHBzOi8vYXV0aC1qcy10ZXN0Lm9rdGEuY29tL29hdXR' + - 'oMi9hdXM4YXVzNzZxOGlwaHVwRDBoNyIsImF1ZCI6Ik5QU' + - '2ZPa0g1ZVpyVHk4UE1EbHZ4IiwiaWF0IjoxNDQ5Njk2MzM' + - 'wLCJleHAiOjE0NDk2OTk5MzAsImp0aSI6IklELlNpOUtxR' + - '3RTV2hLQnJzRGh2bEV0QVItR3lkc2V1Y1VHOXhXdVdLMUp' + - 'oNTgiLCJhbXIiOlsicHdkIl0sImlkcCI6IjAwb2tucjFDS' + - 'GxXYUF3d2dvMGczIiwibm9uY2UiOiJhYWFhYWFhYWFhYWF' + - 'hYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhY' + - 'WFhYWFhYWFhYWFhYWFhYWFhIiwiZW1haWxfdmVyaWZpZWQ' + - 'iOnRydWUsImF1dGhfdGltZSI6MTQ0OTY5NjMzMH0.ekTCW' + - 'khumsT0lXnY-JfzQqfiVkgJzcQoLkvMbtRWb2FG0PYvgTb' + - 'p2MH-lb_Oo6qc2_ZWNieGD7RAhr-dRBXJh8BtDOWR3Zrvp' + - 'Ib_l6Vuv0hDx03tD1WvsbXmwMNsDqFKzR6RGFB-g-Y0Ijc' + - 'qAdJH1xFyb0dPVgj86jb0niRX584'; + tokens.authServerIdToken = 'eyJhbGciOiJSUzI1NiIsImtpZCI6IlU1UjhjSGJHdzQ0NVFicTh6Vk8xUGNDcFhMOHlHNkljb3ZWYTNsYUNveE' + + '0ifQ.eyJzdWIiOiIwMHVrb2VFcUlvZ2lGSHBEZTBnMyIsImVtYWlsIjoic2FtbGphY2tzb25Ab2t0YS5jb20iLCJ2ZXIiOjEsImlzcyI6Imh0dH' + + 'BzOi8vYXV0aC1qcy10ZXN0Lm9rdGEuY29tL29hdXRoMi9hdXM4YXVzNzZxOGlwaHVwRDBoNyIsImF1ZCI6Ik5QU2ZPa0g1ZVpyVHk4UE1EbHZ4I' + + 'iwiaWF0IjoxNDQ5Njk2MzMwLCJleHAiOjE0NDk2OTk5MzAsImp0aSI6IklELlNpOUtxR3RTV2hLQnJzRGh2bEV0QVItR3lkc2V1Y1VHOXhXdVdL' + + 'MUpoNTgiLCJhbXIiOlsicHdkIl0sImlkcCI6IjAwb2tucjFDSGxXYUF3d2dvMGczIiwibm9uY2UiOiJhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWF' + + 'hYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImF1dGhfdGltZSI6MTQ0OTY5Nj' + + 'MzMH0.jy6U2EFPXrwEG7902H2vbcgkHdj7gazYo5TTS1L8jFK6pVSAfw24N1l99oxCJowRn6YnTkV8HIeR2xuBOH6rGGntSFiDl8_GoyX1xM42i' + + 'BH6R1lF9iPWhYBQg0EGKYndCXv215SaHNcxP9D3PEKq78EdUIy9EG9X37lbvVRcbBc'; tokens.authServerIdTokenClaims = { 'sub': '00ukoeEqIogiFHpDe0g3', @@ -191,23 +191,18 @@ define(function() { expiresAt: 1449699930, scopes: ['openid', 'email'], authorizeUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize', - issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7' + issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7', + clientId: 'NPSfOkH5eZrTy8PMDlvx' }; - tokens.modifiedIdToken = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwMHUx' + - 'cGNsYTVxWUlSRURMV0NRViIsIm5hbWUiOiJTYW1sIEphY2tzb24iL' + - 'CJnaXZlbl9uYW1lIjoiU2FtbCIsImZhbWlseV9uYW1lIjoiSmFja3' + - 'NvbiIsInVwZGF0ZWRfYXQiOjE0NDYxNTM0MDEsImVtYWlsIjoic2F' + - 'tbGphY2tzb25Ab2t0YS5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1' + - 'ZSwidmVyIjoxLCJpc3MiOiJodHRwczovL2F1dGgtanMtdGVzdC5va' + - '3RhLmNvbSIsImxvZ2luIjoiYWRtaW5Ab2t0YS5jb20iLCJub25jZS' + - 'I6ImNjY2NjYyIsImF1ZCI6InNvbWVJZCIsImlhdCI6MTQ0OTY5NjM' + - 'zMCwiZXhwIjoxNDQ5Njk5OTMwLCJhbXIiOlsia2JhIiwibWZhIiwi' + - 'cHdkIl0sImp0aSI6IlRSWlQ3UkNpU3ltVHM1VzdSeWgzIiwiYXV0a' + - 'F90aW1lIjoxNDQ5Njk2MzMwfQ.kXzTzaOYLxsVKhlv0DnOOEZEyUw' + - 'Y2MYFVvt3g7ebIZPrvSFfUPfhIxGAlmNWobGo8e7FpFL9Hpip2bVx' + - 'ZZNT4eITptbFv5QA5TzaIudVsMhpngCnqzCwNXen9yEUtne61I6AS' + - 'uXFM_z14ll2Pb0h4mROkionwdApAVARe5I5fVc'; + tokens.modifiedIdToken = 'eyJhbGciOiJSUzI1NiIsImtpZCI6IlU1UjhjSGJHdzQ0NVFicTh6Vk8xUGNDcFhMOHlHNkljb3ZWYTNsYUNveE0i' + + 'fQ.eyJzdWIiOiIwMHUxcGNsYTVxWUlSRURMV0NRViIsIm5hbWUiOiJTYW1sIEphY2tzb24iLCJnaXZlbl9uYW1lIjoiU2FtbCIsImZhbWlseV9u' + + 'YW1lIjoiSmFja3NvbiIsInVwZGF0ZWRfYXQiOjE0NDYxNTM0MDEsImVtYWlsIjoic2FtbGphY2tzb25Ab2t0YS5jb20iLCJlbWFpbF92ZXJpZml' + + 'lZCI6dHJ1ZSwidmVyIjoxLCJpc3MiOiJodHRwczovL2F1dGgtanMtdGVzdC5va3RhLmNvbSIsImxvZ2luIjoiYWRtaW5Ab2t0YS5jb20iLCJub2' + + '5jZSI6ImNjY2NjYyIsImF1ZCI6InNvbWVJZCIsImlhdCI6MTQ0OTY5NjMzMCwiZXhwIjoxNDQ5Njk5OTMwLCJhbXIiOlsia2JhIiwibWZhIiwic' + + 'HdkIl0sImp0aSI6IlRSWlQ3UkNpU3ltVHM1VzdSeWgzIiwiYXV0aF90aW1lIjoxNDQ5Njk2MzMwfQ.lVt8eAGGGUBpyrkTb2aq21wC-d-GEV-SZ' + + 'b8fCupQheQ4GOUEh4Gu2VzRuqFwORYHp177H6b91r7Z9E4L0RbkCLe_F7BmM3JD-BxziFVzIPzKBDZdkg5M12EWomxTd9n-lyYQuE4yA2lOG_W6' + + '6ldl_qLOvGlLTv52mJhOBQxW8ic'; tokens.modifiedIdTokenClaims = { 'sub': '00u1pcla5qYIREDLWCQV', @@ -279,50 +274,17 @@ define(function() { userinfoUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/userinfo' }; - /* - { - 'sub': '00u1pcla5qYIREDLWCQV', - 'name': 'Saml Jackson', - 'given_name': 'Saml', - 'family_name': 'Jackson', - 'updated_at': 1446153401, - 'email': 'samljackson@okta.com', - 'email_verified': true, - 'ver': 1, - 'iss': 'https://auth-js-test.okta.com', - 'login': 'admin@okta.com', - 'nonce': standardNonce, - 'aud': 'NPSfOkH5eZrTy8PMDlvx', - 'iat': 2449696330, - 'exp': 1449699930, - 'amr': [ - 'kba', - 'mfa', - 'pwd' - ], - 'jti': 'TRZT7RCiSymTs5W7Ryh3', - 'auth_time': 1449696330 - } - */ - tokens.expiredBeforeIssuedToken = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzd' + - 'WIiOiIwMHUxcGNsYTVxWUlSRURMV0NRViIsIm5hbWUiOiJTYW1s' + - 'IEphY2tzb24iLCJnaXZlbl9uYW1lIjoiU2FtbCIsImZhbWlseV9' + - 'uYW1lIjoiSmFja3NvbiIsInVwZGF0ZWRfYXQiOjE0NDYxNTM0MD' + - 'EsImVtYWlsIjoic2FtbGphY2tzb25Ab2t0YS5jb20iLCJlbWFpb' + - 'F92ZXJpZmllZCI6dHJ1ZSwidmVyIjoxLCJpc3MiOiJodHRwczov' + - 'L2F1dGgtanMtdGVzdC5va3RhLmNvbSIsImxvZ2luIjoiYWRtaW5' + - 'Ab2t0YS5jb20iLCJub25jZSI6ImFhYWFhYWFhYWFhYWFhYWFhYW' + - 'FhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhY' + - 'WFhYWFhYWEiLCJhdWQiOiJOUFNmT2tINWVaclR5OFBNRGx2eCIs' + - 'ImlhdCI6MjQ0OTY5NjMzMCwiZXhwIjoxNDQ5Njk5OTMwLCJhbXI' + - 'iOlsia2JhIiwibWZhIiwicHdkIl0sImp0aSI6IlRSWlQ3UkNpU3' + - 'ltVHM1VzdSeWgzIiwiYXV0aF90aW1lIjoxNDQ5Njk2MzMwfQ.o1' + - '5xo_fc3Xc-KLxjyD5HxgQcmuVOxRAlcATa8HDzZv04g3CmrgdFN' + - '7W2smjXDgBXFPBFLcgpiDqDioAfnMC6KI0G9a2tJMTjwBLwtYMh' + - 'KZsa4srVE0uXiqdiyiljZ692gdXbwBXgWNIA2PWMrIagxWiqYCn' + - 'fAJcS7TCE611eg-c'; - tokens.standardAuthorizationCode = '35cFyfgCU2u0a1EzAqbO'; + tokens.standardKey = { + alg: 'RS256', + kty: 'RSA', + n: '3ZWrUY0Y6IKN1qI4BhxR2C7oHVFgGPYkd38uGq1jQNSqEvJFcN93CYm16_G78FAFKWqwsJb3Wx-nbxDn6LtP4AhULB1H0K0g7_jLklDAHvI8' + + 'yhOKlvoyvsUFPWtNxlJyh5JJXvkNKV_4Oo12e69f8QCuQ6NpEPl-cSvXIqUYBCs', + e: 'AQAB', + use: 'sig', + kid: 'U5R8cHbGw445Qbq8zVO1PcCpXL8yG6IcovVa3laCoxM' + }; + return tokens; }); diff --git a/test/util/util.js b/test/util/util.js index f2bf31677..20afdd15a 100644 --- a/test/util/util.js +++ b/test/util/util.js @@ -280,14 +280,7 @@ define(function(require) { return setup(options.setup).then(function (test) { return options.execute(test) .fail(function (err) { - - expect(err.message).toEqual(options.errorMsg); - expect(err.errorCode).toEqual('INTERNAL'); - expect(err.errorSummary).toEqual(options.errorMsg); - expect(err.errorLink).toEqual('INTERNAL'); - expect(err.errorId).toEqual('INTERNAL'); - expect(err.errorCauses).toEqual([]); - + util.assertAuthSdkError(err, options.errorMsg); test.ajaxMock.done(); done(); }); @@ -365,5 +358,15 @@ define(function(require) { } }; + util.assertAuthSdkError = function (err, message) { + expect(err.name).toEqual('AuthSdkError'); + expect(err.message).toEqual(message); + expect(err.errorCode).toEqual('INTERNAL'); + expect(err.errorSummary).toEqual(message); + expect(err.errorLink).toEqual('INTERNAL'); + expect(err.errorId).toEqual('INTERNAL'); + expect(err.errorCauses).toEqual([]); + }; + return util; }); diff --git a/test/xhr/keys.js b/test/xhr/keys.js index 81b7945c4..5b51be4a6 100644 --- a/test/xhr/keys.js +++ b/test/xhr/keys.js @@ -2,15 +2,14 @@ define({ "status": 200, "responseType": "json", "response": { + // This is a jwks generated from the RS256 pem on jwt.io "keys": [{ - "e": "AQAB", - "n": "h2OLg6io8KFQz9ucGhKif2CDpbMWPWqOmGkTeb6H0oA9xoaObdHjw0JvQ3Oxv51GgWShBFRY-GedgXcEMvlPvlCsm_00dxiVQGcs4c6YnrImA_nFNlN95DK-JWbVbYlWUU1GKZ7CBnIPUOhlft_b9XwquYmHT3A1HcexHCLm-wXMPB8nGO5k_yspv7S0EFFJioXZ1Vw_N3kbSdT9w9fXZg9aQo6pYd-r0FJqQ4m3R_dky8QF3cofXuAKaV66sv7TzUgASNgDn524my2TfbPy-w2wRzSEN4wkuE-rocFR4NE70hz-vMKT82nJcEPEWEUf-P1YtgjW9J7cvDT_3e4LFQ", - "kty": "RSA", - "use": "sig", - "x5c": [ - "MIIDoDCCAoigAwIBAgIGAUr5X4vRMA0GCSqGSIb3DQEBBQUAMIGQMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEUMBIGA1UECwwLU1NPUHJvdmlkZXIxETAPBgNVBAMMCGxib3lldHRlMRwwGgYJKoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMB4XDTE1MDExNzE5Mjg1NloXDTQ1MDExNzE5Mjk1NlowgZAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ0wCwYDVQQKDARPa3RhMRQwEgYDVQQLDAtTU09Qcm92aWRlcjERMA8GA1UEAwwIbGJveWV0dGUxHDAaBgkqhkiG9w0BCQEWDWluZm9Ab2t0YS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCHY4uDqKjwoVDP25waEqJ/YIOlsxY9ao6YaRN5vofSgD3Gho5t0ePDQm9Dc7G/nUaBZKEEVFj4Z52BdwQy+U++UKyb/TR3GJVAZyzhzpiesiYD+cU2U33kMr4lZtVtiVZRTUYpnsIGcg9Q6GV+39v1fCq5iYdPcDUdx7EcIub7Bcw8HycY7mT/Kym/tLQQUUmKhdnVXD83eRtJ1P3D19dmD1pCjqlh36vQUmpDibdH92TLxAXdyh9e4AppXrqy/tPNSABI2AOfnbibLZN9s/L7DbBHNIQ3jCS4T6uhwVHg0TvSHP68wpPzaclwQ8RYRR/4/Vi2CNb0nty8NP/d7gsVAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAEzXGdup24CLYBkxC2SzwoGJVNAY80qIADGlnJDAUEfNPRZVto/7LHWj6tQ7UjMvSB0kHVKVzc0SVAnJMzgH1YzUfcdPPywVkDjGVzn1979aBugjxO+HWL0M1ZrNQ77HArR+dwZqOElaN3Wpz76SwKhtEwNczSUL+h2mIbMMt/zoK0Fv1jWrpNPv3gAY+6LGsgv+QZoYrOO+m+Kz1vMBmLA3BOVzLJhaDK0avthRKIRbOs6NW8dicPJ+nlM6fWuKwnc960EwNixazEQ2F9ALKU5Xrvx9uB92us3+lW9k6zssSSDzB9y6BjlXEwM0URu+2drk7S6R1TOQPlYtvM7a7Ho=" - ], - "x5t": "R32_Kco0W4FIxTznZzHBVPtING0" + alg: 'RS256', + kty: 'RSA', + n: '3ZWrUY0Y6IKN1qI4BhxR2C7oHVFgGPYkd38uGq1jQNSqEvJFcN93CYm16_G78FAFKWqwsJb3Wx-nbxDn6LtP4AhULB1H0K0g7_jLklDAHvI8yhOKlvoyvsUFPWtNxlJyh5JJXvkNKV_4Oo12e69f8QCuQ6NpEPl-cSvXIqUYBCs', + e: 'AQAB', + use: 'sig', + kid: 'U5R8cHbGw445Qbq8zVO1PcCpXL8yG6IcovVa3laCoxM' }] } }); diff --git a/test/xhr/well-known-shared-resource.js b/test/xhr/well-known-shared-resource.js new file mode 100644 index 000000000..34791a38a --- /dev/null +++ b/test/xhr/well-known-shared-resource.js @@ -0,0 +1,53 @@ +define({ + "status": 200, + "responseType": "json", + "response": { + "issuer": "https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7", + "authorization_endpoint": "https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize", + "token_endpoint": "https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/token", + "jwks_uri": "https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/keys", + "response_types_supported": [ + "code", + "token", + "code token" + ], + "response_modes_supported": [ + "query", + "fragment", + "form_post", + "okta_post_message" + ], + "grant_types_supported": [ + "authorization_code", + "implicit", + "refresh_token", + "password" + ], + "subject_types_supported": [ + "public" + ], + "scopes_supported": [ + "offline_access" + ], + "token_endpoint_auth_methods_supported": [ + "client_secret_basic", + "client_secret_post", + "none" + ], + "code_challenge_methods_supported": [ + "S256" + ], + "introspection_endpoint": "https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/introspect", + "introspection_endpoint_auth_methods_supported": [ + "client_secret_basic", + "client_secret_post", + "none" + ], + "revocation_endpoint": "https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/revoke", + "revocation_endpoint_auth_methods_supported": [ + "client_secret_basic", + "client_secret_post", + "none" + ] + } +}); diff --git a/test/xhr/well-known.js b/test/xhr/well-known.js index 990c9b98f..ceac9f51c 100644 --- a/test/xhr/well-known.js +++ b/test/xhr/well-known.js @@ -2,10 +2,10 @@ define({ "status": 200, "responseType": "json", "response": { - "issuer": "<%= uri %>", - "authorization_endpoint": "<%= uri %>/oauth2/v1/authorize", - "userinfo_endpoint": "<%= uri %>/oauth2/v1/userinfo", - "jwks_uri": "<%= uri %>/oauth2/v1/keys", + "issuer": "https://auth-js-test.okta.com", + "authorization_endpoint": "https://auth-js-test.okta.com/oauth2/v1/authorize", + "userinfo_endpoint": "https://auth-js-test.okta.com/oauth2/v1/userinfo", + "jwks_uri": "https://auth-js-test.okta.com/oauth2/v1/keys", "response_types_supported": [ "id_token", "id_token token"