@okta-dfuhriman/okta-auth-js
Version:
The Okta Auth SDK
161 lines (142 loc) • 5.33 kB
text/typescript
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable complexity, max-statements */
/*!
* Copyright (c) 2015-present, Okta, Inc. and/or its affiliates. All rights reserved.
* The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
*
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and limitations under the License.
*
*/
import { clone } from '../util';
import {
getOAuthUrls,
} from './util/oauth';
import { AuthSdkError, OAuthError } from '../errors';
import {
OktaAuthOAuthInterface,
TokenVerifyParams,
IDToken,
OAuthResponse,
TokenParams,
TokenResponse,
CustomUrls,
Tokens,
} from './types';
import { verifyToken } from './verifyToken';
import { getDefaultTokenParams } from './util';
function validateResponse(res: OAuthResponse, oauthParams: TokenParams) {
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');
}
}
export async function handleOAuthResponse(
sdk: OktaAuthOAuthInterface,
tokenParams: TokenParams,
res: OAuthResponse,
urls?: CustomUrls
): Promise<TokenResponse> {
const pkce = sdk.options.pkce !== false;
const par = sdk?.options?.pushAuthorizationRequest ?? sdk.transactionManager?.load()?.clientAssertion;
// The result contains an authorization_code and PKCE is enabled
// OR the saved transaction contains a `clientAssertion`
// `exchangeCodeForTokens` will call /token then call `handleOauthResponse` recursively with the result
if ((par || pkce) && (res?.code || res?.interaction_code)) {
return sdk.token.exchangeCodeForTokens(Object.assign({}, tokenParams, {
authorizationCode: res.code,
interactionCode: res.interaction_code
}), urls);
}
tokenParams = tokenParams || getDefaultTokenParams(sdk);
urls = urls || getOAuthUrls(sdk, tokenParams);
let responseType = tokenParams.responseType || [];
if (!Array.isArray(responseType) && responseType !== 'none') {
responseType = [responseType];
}
let scopes;
if (res.scope) {
scopes = res.scope.split(' ');
} else {
scopes = clone(tokenParams.scopes);
}
const clientId = tokenParams.clientId || sdk.options.clientId;
// Handling the result from implicit flow or PKCE token exchange
validateResponse(res, tokenParams);
const tokenDict = {} as Tokens;
const expiresIn = res.expires_in;
const tokenType = res.token_type;
const accessToken = res.access_token;
const idToken = res.id_token;
const refreshToken = res.refresh_token;
const now = Math.floor(Date.now()/1000);
if (accessToken) {
const accessJwt = sdk.token.decode(accessToken);
tokenDict.accessToken = {
accessToken: accessToken,
claims: accessJwt.payload,
expiresAt: Number(expiresIn) + now,
tokenType: tokenType!,
scopes: scopes,
authorizeUrl: urls.authorizeUrl!,
userinfoUrl: urls.userinfoUrl!
};
}
if (refreshToken) {
tokenDict.refreshToken = {
refreshToken: refreshToken,
// should not be used, this is the accessToken expire time
// TODO: remove "expiresAt" in the next major version OKTA-407224
expiresAt: Number(expiresIn) + now,
scopes: scopes,
tokenUrl: urls.tokenUrl!,
authorizeUrl: urls.authorizeUrl!,
issuer: urls.issuer!,
};
}
if (idToken) {
const idJwt = sdk.token.decode(idToken);
const idTokenObj: IDToken = {
idToken: idToken,
claims: idJwt.payload,
expiresAt: idJwt.payload.exp! - idJwt.payload.iat! + now, // adjusting expiresAt to be in local time
scopes: scopes,
authorizeUrl: urls.authorizeUrl!,
issuer: urls.issuer!,
clientId: clientId!
};
const validationParams: TokenVerifyParams = {
clientId: clientId!,
issuer: urls.issuer!,
nonce: tokenParams.nonce,
accessToken: accessToken,
acrValues: tokenParams.acrValues
};
if (tokenParams.ignoreSignature !== undefined) {
validationParams.ignoreSignature = tokenParams.ignoreSignature;
}
await verifyToken(sdk, idTokenObj, validationParams);
tokenDict.idToken = idTokenObj;
}
// Validate received tokens against requested response types
if (responseType.indexOf('token') !== -1 && !tokenDict.accessToken) {
// eslint-disable-next-line max-len
throw new AuthSdkError('Unable to parse OAuth flow response: response type "token" was requested but "access_token" was not returned.');
}
if (responseType.indexOf('id_token') !== -1 && !tokenDict.idToken) {
// eslint-disable-next-line max-len
throw new AuthSdkError('Unable to parse OAuth flow response: response type "id_token" was requested but "id_token" was not returned.');
}
return {
tokens: tokenDict,
state: res.state!,
code: res.code,
responseType
};
}