-
Notifications
You must be signed in to change notification settings - Fork 271
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added token.verify and deprecated idToken.verify #49
Changes from all commits
f6aafb9
067eba7
e7fc3fd
1fc314a
1672f66
0454de8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We dont expect validateClaims return any info before we proceed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. validateClaims is a synchronous method that throws errors. validateClaims could be renamed to assertClaims, but it does additional validation that makes the term "assert" feel inaccurate. |
||
|
||
// 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']) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a big fan of tracking the tokenDict state outside of the promise chain approach. Couple alternatives:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure why 1 is preferred over 2. To do the sorting of the tokens (and checking for the presence of an idToken or accessToken), it seems more reasonable to return a tokenDict and build the array of tokens in this block. |
||
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 | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a metadata tag that specifies the intent of how the key should be used. It's not necessary to properly verify the key's signature, per @yuliu-okta.