Skip to content

Commit

Permalink
feat: enable extraParams to persist to token refresh (#1529)
Browse files Browse the repository at this point in the history
OKTA-750886 fix: persist `extraParams` passed to `/authorize` and include them during token refresh
  • Loading branch information
jaredperreault-okta authored Aug 14, 2024
1 parent 7deec3d commit d89b413
Show file tree
Hide file tree
Showing 26 changed files with 185 additions and 44 deletions.
17 changes: 15 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
# Changelog

## 7.6.0
# 7.7.1

- [#1529](https://github.com/okta/okta-auth-js/pull/1529) fix: persist `extraParams` passed to `/authorize` and include them during token refresh

## 7.7.0

### Features

- [#1495](https://github.com/okta/okta-auth-js/pull/1495) add: DPoP support

### Fixes

- [#1508](https://github.com/okta/okta-auth-js/pull/1508) IDX: add condition to compare stateHandles when loading saved idxResponse only when useGenericRemediator option is false or undefined


## 7.6.0

### Features

- [#1507](https://github.com/okta/okta-auth-js/pull/1507) add: new method `getOrRenewAccessToken`
- [#1505](https://github.com/okta/okta-auth-js/pull/1505) add: support of `revokeSessions` param for `OktaPassword` authenticator (can be used in `reset-authenticator` remediation)
- [#1508](https://github.com/okta/okta-auth-js/pull/1508) IDX: add condition to compare stateHandles when loading saved idxResponse only when useGenericRemediator option is false or undefined
- [#1512](https://github.com/okta/okta-auth-js/pull/1512) add: new service `RenewOnTabActivation`

### Bug Fix
Expand Down
7 changes: 6 additions & 1 deletion lib/oidc/endpoints/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,13 @@ export async function postRefreshToken(
return name + '=' + encodeURIComponent(value!);
}).join('&');

let url = refreshToken.tokenUrl;
if (options.extraParams && Object.keys(options.extraParams).length >= 1) {
url += toQueryString(options.extraParams);
}

const params: TokenRequestParams = {
url: refreshToken.tokenUrl,
url,
data,
dpopKeyPair: options?.dpopKeyPair
};
Expand Down
2 changes: 2 additions & 0 deletions lib/oidc/exchangeCodeForTokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export async function exchangeCodeForTokens(sdk: OktaAuthOAuthInterface, tokenPa
acrValues,
dpop,
dpopPairId,
extraParams
} = tokenParams;

// postToTokenEndpoint() params
Expand Down Expand Up @@ -64,6 +65,7 @@ export async function exchangeCodeForTokens(sdk: OktaAuthOAuthInterface, tokenPa
responseType,
ignoreSignature,
acrValues,
extraParams
};

try {
Expand Down
13 changes: 13 additions & 0 deletions lib/oidc/handleOAuthResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export async function handleOAuthResponse(
): Promise<TokenResponse> {
const pkce = sdk.options.pkce !== false;


// The result contains an authorization_code and PKCE is enabled
// `exchangeCodeForTokens` will call /token then call `handleOauthResponse` recursively with the result
if (pkce && (res.code || res.interaction_code)) {
Expand Down Expand Up @@ -106,6 +107,10 @@ export async function handleOAuthResponse(
if (tokenParams.dpopPairId) {
tokenDict.accessToken.dpopPairId = tokenParams.dpopPairId;
}

if (tokenParams.extraParams) {
tokenDict.accessToken.extraParams = tokenParams.extraParams;
}
}

if (refreshToken) {
Expand All @@ -123,6 +128,10 @@ export async function handleOAuthResponse(
if (tokenParams.dpopPairId) {
tokenDict.refreshToken.dpopPairId = tokenParams.dpopPairId;
}

if (tokenParams.extraParams) {
tokenDict.refreshToken.extraParams = tokenParams.extraParams;
}
}

if (idToken) {
Expand All @@ -137,6 +146,10 @@ export async function handleOAuthResponse(
clientId: clientId!
};

if (tokenParams.extraParams) {
idTokenObj.extraParams = tokenParams.extraParams;
}

const validationParams: TokenVerifyParams = {
clientId: clientId!,
issuer: urls.issuer!,
Expand Down
5 changes: 3 additions & 2 deletions lib/oidc/renewToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,15 @@ export async function renewToken(sdk: OktaAuthOAuthInterface, token: Token): Pro
responseType = 'id_token';
}

const { scopes, authorizeUrl, userinfoUrl, issuer, dpopPairId } = token as (AccessToken & IDToken);
const { scopes, authorizeUrl, userinfoUrl, issuer, dpopPairId, extraParams } = token as (AccessToken & IDToken);
return getWithoutPrompt(sdk, {
responseType,
scopes,
authorizeUrl,
userinfoUrl,
issuer,
dpopPairId
dpopPairId,
extraParams
})
.then(function (res) {
return getSingleToken(token, res.tokens);
Expand Down
4 changes: 3 additions & 1 deletion lib/oidc/renewTokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,16 @@ export async function renewTokens(sdk, options?: RenewTokensParams): Promise<Tok
const userinfoUrl = accessToken.userinfoUrl || sdk.options.userinfoUrl;
const issuer = idToken.issuer || sdk.options.issuer;
const dpopPairId = accessToken?.dpopPairId;
const extraParams = accessToken?.extraParams || idToken?.extraParams;

// Get tokens using the SSO cookie
options = Object.assign({
scopes,
authorizeUrl,
userinfoUrl,
issuer,
dpopPairId
dpopPairId,
extraParams
}, options);

if (sdk.options.pkce) {
Expand Down
6 changes: 6 additions & 0 deletions lib/oidc/renewTokensWithRefresh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { TokenEndpointParams, postRefreshToken } from './endpoints/token';
import { findKeyPair } from './dpop';
import { isRefreshTokenInvalidError } from './util/errors';

/* eslint complexity:[0,8] */
export async function renewTokensWithRefresh(
sdk: OktaAuthOAuthInterface,
tokenParams: TokenParams,
Expand All @@ -31,6 +32,11 @@ export async function renewTokensWithRefresh(

try {
const renewTokenParams: TokenParams = Object.assign({}, tokenParams, { clientId });

if (refreshTokenObject.extraParams) {
renewTokenParams.extraParams = refreshTokenObject.extraParams;
}

const endpointParams: TokenEndpointParams = {...renewTokenParams};

if (dpop) {
Expand Down
1 change: 1 addition & 0 deletions lib/oidc/types/Token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface AbstractToken {
authorizeUrl: string;
scopes: string[];
pendingRemove?: boolean;
extraParams?: Record<string, string>;
}

export interface AccessToken extends AbstractToken {
Expand Down
3 changes: 2 additions & 1 deletion lib/oidc/types/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ export interface OAuthTransactionMeta extends
'ignoreSignature' |
'nonce' |
'acrValues' |
'enrollAmrValues'
'enrollAmrValues' |
'extraParams'
>
{
urls: CustomUrls;
Expand Down
1 change: 1 addition & 0 deletions lib/oidc/util/oauthMeta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export function createOAuthMeta(
nonce: tokenParams.nonce!,
ignoreSignature: tokenParams.ignoreSignature!,
acrValues: tokenParams.acrValues,
extraParams: tokenParams.extraParams
};

if (tokenParams.pkce === false) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"private": true,
"name": "@okta/okta-auth-js",
"description": "The Okta Auth SDK",
"version": "7.7.0",
"version": "7.7.1",
"homepage": "https://github.com/okta/okta-auth-js",
"license": "Apache-2.0",
"main": "build/cjs/exports/default.js",
Expand Down
2 changes: 1 addition & 1 deletion samples/test/cucumber.wdio.conf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const USE_FIREFOX = !!process.env.USE_FIREFOX;
const DEBUG = process.env.DEBUG;
const CI = process.env.CI;
const LOG = process.env.LOG;
const defaultTimeoutInterval = DEBUG ? (24 * 60 * 60 * 1000) : 10000;
const defaultTimeoutInterval = DEBUG ? (24 * 60 * 60 * 1000) : 15000;
const logLevel = LOG || 'warn';
const chromeOptions = {
args: []
Expand Down
4 changes: 2 additions & 2 deletions scripts/e2e/e2e-mfa.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export CLIENT_ID=0oa41zpqqxar7OFl84x7
export SPA_CLIENT_ID=0oa41zpqqxar7OFl84x7
export MFA_ENABLED=true

get_vault_secret_key devex/auth-js-sdk-vars security_question_answer SECURITY_QUESTION_ANSWER
get_vault_secret_key devex/auth-js-sdk-vars a18n_api_key A18N_API_KEY
get_terminus_secret "/" security_question_answer SECURITY_QUESTION_ANSWER
get_terminus_secret "/" a18n_api_key A18N_API_KEY

run_e2e
2 changes: 1 addition & 1 deletion scripts/e2e/e2e-saucelabs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export TEST_RESULT_FILE_DIR="${REPO}/build2/reports/e2e-saucelabs"

# run e2e tests with test/e2e/sauce.wdio.conf.js config
export CLIENT_ID=0oa1xyzajgPFGWlLP4x7
get_vault_secret_key devex/sauce-labs accessKey SAUCE_ACCESS_KEY
get_terminus_secret "/" accessKey SAUCE_ACCESS_KEY

export RUN_SAUCE_TESTS=true
export SAUCE_USERNAME=OktaSignInWidget
Expand Down
4 changes: 2 additions & 2 deletions scripts/integration.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ export TEST_RESULT_FILE_DIR="${REPO}/build2/reports/unit"
export CI=true
export ISSUER=https://javascript-idx-sdk-idfirst.okta.com
export USERNAME=george@acme.com
get_vault_secret_key repo_gh-okta-okta-auth-js/default password PASSWORD
get_terminus_secret "/" password PASSWORD

# for myaccount password API testing
export PASSWORDLESS_USERNAME=password.optional@mailinator.com
get_vault_secret_key devex/auth-js-sdk-vars a18n_api_key A18N_API_KEY
get_terminus_secret "/" a18n_api_key A18N_API_KEY

# This client has refresh token enabled and password optional
export CLIENT_ID=0oa3b5fp4h02UIrjZ697
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export SAMPLE_NAME=@okta/samples.express-embedded-auth-with-sdk
export MAX_INSTANCES=1

# NOTE: the command below evaluates to the same PASSWORD retrieved in setup-e2e, leaving commented just in case
# get_vault_secret_key devex/prod-js-idx-sdk-vars password PASSWORD
# get_terminus_secret "/" password PASSWORD

# based on run_sample_tests
create_log_group "E2E Test Run"
Expand Down
2 changes: 1 addition & 1 deletion scripts/samples/e2e-express-embedded-auth-with-sdk-spec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export SAMPLE_NAME=@okta/samples.express-embedded-auth-with-sdk
export MAX_INSTANCES=1

# NOTE: the command below evaluates to the same PASSWORD retrieved in setup-e2e, leaving commented just in case
# get_vault_secret_key devex/prod-js-idx-sdk-vars password PASSWORD
# get_terminus_secret "/" password PASSWORD

# based on run_sample_tests
create_log_group "E2E Test Run"
Expand Down
2 changes: 1 addition & 1 deletion scripts/samples/e2e-express-embedded-auth-with-sdk.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export SAMPLE_NAME=@okta/samples.express-embedded-auth-with-sdk
export MAX_INSTANCES=1

# NOTE: the command below evaluates to the same PASSWORD retrieved in setup-e2e, leaving commented just in case
# get_vault_secret_key devex/prod-js-idx-sdk-vars password PASSWORD
# get_terminus_secret "/" password PASSWORD

# based on run_sample_tests
create_log_group "E2E Spec Test Run"
Expand Down
67 changes: 48 additions & 19 deletions scripts/setup-e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,33 @@ if [ -n "${TEST_SUITE_ID}" ]; then
setup_service java 1.8.222

# this chrome install is not used, however it will install linux deps chrome needs (via apt-get)
setup_service google-chrome-stable 118.0.5993.70-1
# setup_service google-chrome-stable 127.0.6533.88-1
setup_service google-chrome-stable 121.0.6167.85-1
# uses new chrome for testing installation utility (https://developer.chrome.com/blog/chrome-for-testing/)
# output format: chrome@118.0.5993.70 /path/to/chrome/binary
CHROME_INSTALL=$(npx @puppeteer/browsers install chrome@stable)
echo $CHROME_INSTALL
# extract installed version
export CHROMEDRIVER_VERSION=$(echo $CHROME_INSTALL | awk '{print $1}' | cut -d'@' -f 2)
echo $CHROMEDRIVER_VERSION
# extract binary path
export CHROME_BINARY=$(echo $CHROME_INSTALL | awk '{print $2}')
echo $CHROME_BINARY
# npm i -g @puppeteer/browsers@1.x
# @puppeteer/browsers install chrome@stable]

# OLD_NPM_REGISTRY=$(npm config get registry)
# npm config set registry https://registry.npmjs.org
# npm config get registry

# echo "Running npx @puppeteer/browsers"
# npx @puppeteer/browsers install chrome@stable --version
# npx @puppeteer/browsers install chrome@stable
# echo "Running puppeteer install"
# CHROME_INSTALL=$(npx @puppeteer/browsers@1.x install chrome@stable)
# echo "CHROME_INSTALL: $CHROME_INSTALL"
# # extract installed version
# export CHROMEDRIVER_VERSION=$(echo $CHROME_INSTALL | awk '{print $1}' | cut -d'@' -f 2)
# echo $CHROMEDRIVER_VERSION
# # extract binary path
# export CHROME_BINARY=$(echo $CHROME_INSTALL | awk '{print $2}')
# echo $CHROME_BINARY

# npm config set registry $OLD_NPM_REGISTRY
# wget -v https://storage.googleapis.com/chrome-for-testing-public/127.0.6533.88/linux64/chrome-linux64.zip
# echo $(ls)

export CI=true
else
Expand All @@ -45,12 +61,18 @@ setup_e2e () {

export ISSUER=https://samples-javascript.okta.com/oauth2/default
export USERNAME=george@acme.com
get_vault_secret_key repo_gh-okta-okta-auth-js/default password PASSWORD
get_terminus_secret "/" password PASSWORD
finish_log_group $?
}

run_e2e () {
create_log_group "E2E Test Run"

if [ -z "${PASSWORD}" ]; then
echo "No PASSWORD has been set! Exiting..."
exit ${TEST_FAILURE}
fi

if [ -n "${RUN_CUCUMBER}" ]; then
if ! yarn test:e2e:cucumber; then
echo "Cucumber tests failed! Exiting..."
Expand All @@ -76,42 +98,49 @@ setup_sample_tests () {
export TEST_RESULT_FILE_DIR="${REPO}/build2/reports/e2e"

export USERNAME=mary@acme.com
get_vault_secret_key repo_gh-okta-okta-auth-js/default password PASSWORD
get_terminus_secret "/" password PASSWORD

export ORG_OIE_ENABLED=true
get_vault_secret_key devex/auth-js-sdk-vars a18n_api_key A18N_API_KEY
get_terminus_secret "/" a18n_api_key A18N_API_KEY


# If this script is run as a bacon task, run against trexcloud environment
if [[ "${BACON_TASK}" == true ]]; then
echo "Running tests against trexcloud org"
export ISSUER=https://javascript-idx-sdk.trexcloud.com
export CLIENT_ID=0oa3r1keeeFFb7VMG0g7
export SPA_CLIENT_ID=0oa3r92jj01DWBeWC0g7
get_vault_secret_key devex/trex-js-idx-sdk-vars trex_client_secret CLIENT_SECRET
get_vault_secret_key devex/trex-js-idx-sdk-vars trex_idx_sdk_org_api_key OKTA_API_KEY
get_terminus_secret "/" trex_client_secret CLIENT_SECRET
get_terminus_secret "/" trex_idx_sdk_org_api_key OKTA_API_KEY
else
if [ -n "$USE_OK_14" ]; then
echo "Running tests against production (ok14) org"
export CLIENT_ID=0oax3dcx0sak1KKb9696
export ISSUER=https://javascript-idx-sdk-new.okta.com
export ISSUER_IDFIRST=https://javascript-idx-sdk-idfirst.okta.com
get_vault_secret_key devex/prod-js-idx-sdk-vars prod_client_secret_new CLIENT_SECRET
get_vault_secret_key devex/prod-js-idx-sdk-vars prod_idx_sdk_org_api_key_new OKTA_API_KEY
get_vault_secret_key devex/prod-js-idx-sdk-vars prod_idx_idfirst_sdk_org_api_key OKTA_API_KEY_IDFIRST
get_terminus_secret "/prod/idx" prod_client_secret_new CLIENT_SECRET
get_terminus_secret "/prod/idx" prod_idx_sdk_org_api_key_new OKTA_API_KEY
get_terminus_secret "/prod/idx" prod_idx_idfirst_sdk_org_api_key OKTA_API_KEY_IDFIRST
else
echo "Running tests against production (ok12) org"
export ISSUER=https://javascript-idx-sdk.okta.com
export CLIENT_ID=0oav2oxnlYjULp0Cy5d6
export SPA_CLIENT_ID=0oa17suj5x9khaVH75d7
get_vault_secret_key devex/prod-js-idx-sdk-vars prod_client_secret CLIENT_SECRET
get_vault_secret_key devex/prod-js-idx-sdk-vars prod_idx_sdk_org_api_key OKTA_API_KEY
get_terminus_secret "/prod/idx" prod_client_secret CLIENT_SECRET
get_terminus_secret "/prod/idx" prod_idx_sdk_org_api_key OKTA_API_KEY
fi
fi
finish_log_group $?
}

run_sample_tests () {
create_log_group "E2E Test Run"

if [ -z "${PASSWORD}" ]; then
echo "No PASSWORD has been set! Exiting..."
exit ${TEST_FAILURE}
fi

# Run the tests
if ! yarn test:samples; then
echo "tests failed! Exiting..."
Expand Down
8 changes: 8 additions & 0 deletions scripts/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ else
fi
}

get_terminus_secret () {
# ensures the env var is set
if [ -z "$(echo "$3")" ]; then
echo "$3 is not defined. Exiting..."
exit 1
fi
}

junit () {
echo 'noop'
}
Expand Down
Loading

0 comments on commit d89b413

Please sign in to comment.