Skip to content

Commit 22ab3b9

Browse files
feat: adds dpopOtions.allowBearerTokens configuration (#1584)
OKTA-909903 feat: adds dpopOptions.allowBearerTokens config
1 parent bc0fb16 commit 22ab3b9

File tree

6 files changed

+65
-7
lines changed

6 files changed

+65
-7
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
- A [`Cross-Origin-Opener-Policy`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy) resilient method of acquiring tokens using via external IDPs.
99
- See [documentation](https://github.com/okta/okta-auth-js?tab=readme-ov-file#tokengetwithidppopupoptions) for more detailed explanation
1010

11+
- [#1584](https://github.com/okta/okta-auth-js/pull/1584) feat: adds `dpopOptions.allowBearerTokens` configuration
12+
1113
# 7.11.3
1214

1315
### Fixes

README.md

+18
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,10 @@ const config = {
415415
// other configurations
416416
pkce: true, // required
417417
dpop: true,
418+
dpopOptions: {
419+
// set to `true` to skip the validation to check the resulting token response includes `token_type: DPoP`
420+
allowBearerTokens: false // defaults to `false`, tokens are validated to include `token_type: DPoP`
421+
}
418422
};
419423

420424
const authClient = new OktaAuth(config);
@@ -575,6 +579,20 @@ Default value is `false`. Set to `true` to enable `DPoP` (Demonstrating Proof-of
575579

576580
See Guide: [Enabling DPoP](#enabling-dpop)
577581

582+
#### `dpopOptions`
583+
584+
Default value:
585+
```javascript
586+
dpopOptions: {
587+
allowBearerTokens: false
588+
}
589+
```
590+
591+
See Guide: [Enabling DPoP](#enabling-dpop)
592+
593+
#### `dpopOptions.allowBearerTokens`
594+
595+
When `false`, dpop-enabled token requests are validated to contain `token_type: DPoP` and will throw otherwise. Set to `true` to skip this validation and allow `Bearer` tokens as a possible `token_type`. This can be useful during a migration, to avoid needing to update a web application simutaneously with Okta Org configurations. Defaults to `false`
578596

579597
#### responseMode
580598

lib/oidc/handleOAuthResponse.ts

+10-6
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,6 @@ function validateResponse(res: OAuthResponse, oauthParams: TokenParams) {
3939
if (res.state !== oauthParams.state) {
4040
throw new AuthSdkError('OAuth flow response state doesn\'t match request state');
4141
}
42-
43-
// https://datatracker.ietf.org/doc/html/rfc9449#token-response
44-
// "A token_type of DPoP MUST be included in the access token response to signal to the client"
45-
if (oauthParams.dpop && res.token_type !== 'DPoP') {
46-
throw new AuthSdkError('Unable to parse OAuth flow response: DPoP was configured but "token_type" was not DPoP');
47-
}
4842
}
4943

5044
export async function handleOAuthResponse(
@@ -83,6 +77,16 @@ export async function handleOAuthResponse(
8377
// Handling the result from implicit flow or PKCE token exchange
8478
validateResponse(res, tokenParams);
8579

80+
if (tokenParams.dpop) {
81+
const { allowBearerTokens } = sdk.options?.dpopOptions ?? { allowBearerTokens: false };
82+
83+
// https://datatracker.ietf.org/doc/html/rfc9449#token-response
84+
// "A token_type of DPoP MUST be included in the access token response to signal to the client"
85+
if (!allowBearerTokens && res.token_type !== 'DPoP') {
86+
throw new AuthSdkError('Unable to parse OAuth flow response: DPoP was configured but "token_type" was not DPoP');
87+
}
88+
}
89+
8690
const tokenDict = {} as Tokens;
8791
const expiresIn = res.expires_in;
8892
const tokenType = res.token_type;

lib/oidc/options/OAuthOptionsConstructor.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ import {
2121
OktaAuthOAuthOptions,
2222
SetLocationFunction,
2323
TokenManagerOptions,
24-
TransactionManagerOptions
24+
TransactionManagerOptions,
25+
DPoPOptions
2526
} from '../types';
2627
import { enableSharedStorage } from './node';
2728
import AuthSdkError from '../../errors/AuthSdkError';
@@ -82,6 +83,7 @@ export function createOAuthOptionsConstructor() {
8283
acrValues: string;
8384
maxAge: string | number;
8485
dpop: boolean;
86+
dpopOptions: DPoPOptions;
8587

8688
// Additional options
8789
tokenManager: TokenManagerOptions;
@@ -128,6 +130,10 @@ export function createOAuthOptionsConstructor() {
128130
this.acrValues = options.acrValues;
129131
this.maxAge = options.maxAge;
130132
this.dpop = options.dpop === true; // dpop defaults to false
133+
this.dpopOptions = {
134+
allowBearerTokens: false,
135+
...options.dpopOptions,
136+
};
131137

132138
this.tokenManager = options.tokenManager;
133139
this.postLogoutRedirectUri = options.postLogoutRedirectUri;

lib/oidc/types/options.ts

+5
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ export interface RenewTokensParams extends TokenParams {
8383
tokens?: Tokens
8484
}
8585

86+
export interface DPoPOptions {
87+
allowBearerTokens: boolean;
88+
}
89+
8690
export interface OktaAuthOAuthOptions extends
8791
OktaAuthHttpOptions,
8892
CustomUrls,
@@ -108,6 +112,7 @@ export interface OktaAuthOAuthOptions extends
108112
maxClockSkew?: number;
109113
restoreOriginalUri?: (oktaAuth: OktaAuthOAuthInterface, originalUri?: string) => Promise<void>;
110114
dpop?: boolean;
115+
dpopOptions?: DPoPOptions;
111116

112117
transactionManager?: TransactionManagerOptions;
113118

test/spec/oidc/util/handleOAuthResponse.ts

+23
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,29 @@ describe('handleOAuthResponse', () => {
222222
}, undefined);
223223
expect(res).toBe(mockTokens);
224224
});
225+
226+
it('allows Bearer tokens to be returned when DPoP token was requested', async () => {
227+
sdk = mockOktaAuth({
228+
dpopOptions: { allowBearerTokens: true }
229+
});
230+
const tokenParams: TokenParams = {
231+
responseType: ['token', 'id_token', 'refresh_token'],
232+
dpop: true,
233+
extraParams: { foo: 'bar' }
234+
};
235+
const oauthRes = { id_token: 'foo', access_token: 'blar', refresh_token: 'bloo', token_type: 'Bearer' };
236+
const res = await handleOAuthResponse(sdk, tokenParams, oauthRes, undefined as unknown as CustomUrls);
237+
expect(res.tokens).toBeTruthy();
238+
expect(res.tokens.accessToken).toBeTruthy();
239+
expect(res.tokens.accessToken!.accessToken).toBe('blar');
240+
expect(res.tokens.accessToken!.extraParams).toEqual({ foo: 'bar' });
241+
expect(res.tokens.idToken).toBeTruthy();
242+
expect(res.tokens.idToken!.idToken).toBe('foo');
243+
expect(res.tokens.idToken!.extraParams).toEqual({ foo: 'bar' });
244+
expect(res.tokens.refreshToken).toBeTruthy();
245+
expect(res.tokens.refreshToken!.refreshToken).toBe('bloo');
246+
expect(res.tokens.refreshToken!.extraParams).toEqual({ foo: 'bar' });
247+
});
225248
});
226249

227250
describe('Interaction code flow', () => {

0 commit comments

Comments
 (0)