Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions packages/auth0-auth-js/EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,9 +349,12 @@ const loginHint = '<login_hint>';
const tokenResponseForGoogle = await authClient.getTokenForConnection({ connection, refreshToken });
```

- `refreshToken`: The refresh token to use to retrieve the access token.
- `refreshToken`: The refresh token to use to retrieve the access token for the connection.
- `accessToken`: The access token to use to exchange for an access token for the connection.
- `connection`: The connection for which an access token should be retrieved, e.g. `google-oauth2` for Google.
- `loginHint`: Optional login hint to inform which connection account to use, can be useful when multiple accounts for the connection exist for the same user.
- `loginHint`: Optional login hint to inform which connection account to use, can be useful when multiple accounts for the connection exist for the same user.

Either the `refreshToken` or `accessToken` parameter can be specified, but not both.

Note that, when using `google-oauth2`, it's required to set both `authorizationParams.access_type` and `authorizationParams.prompt` to `offline` and `consent` respectively when building the authorization URL.

Expand Down
61 changes: 60 additions & 1 deletion packages/auth0-auth-js/src/auth-client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,7 @@ test('getTokenByRefreshToken - should throw when token exchange failed', async (
);
});

test('getTokenForConnection - should return the tokens', async () => {
test('getTokenForConnection - should return the tokens when called with a refresh token subject token', async () => {
const authClient = new AuthClient({
domain,
clientId: '<client_id>',
Expand All @@ -756,6 +756,65 @@ test('getTokenForConnection - should return the tokens', async () => {
expect(result.accessToken).toBe(accessToken);
});

test('getTokenForConnection - should return the tokens when called with an access token subject token', async () => {
const authClient = new AuthClient({
domain,
clientId: '<client_id>',
clientSecret: '<client_secret>',
});

const result = await authClient.getTokenForConnection({
connection: '<connection>',
accessToken: '<access_token>',
loginHint: '<sub>',
});

expect(result).toBeDefined();
expect(result.accessToken).toBe(accessToken);
});

test('getTokenForConnection - should throw when both an access and refresh tokens are specified', async () => {
const authClient = new AuthClient({
domain,
clientId: '<client_id>',
clientSecret: '<client_secret>',
});

await expect(
authClient.getTokenForConnection({
connection: '<connection>',
refreshToken: '<refresh_token>',
accessToken: '<access_token>',
})
).rejects.toThrowError(
expect.objectContaining({
code: 'token_for_connection_error',
message:
'Either a refresh or access token should be specified, but not both.'
})
);
});

test('getTokenForConnection - should throw when neither an access nor a refresh token is specified', async () => {
const authClient = new AuthClient({
domain,
clientId: '<client_id>',
clientSecret: '<client_secret>',
});

await expect(
authClient.getTokenForConnection({
connection: '<connection>',
})
).rejects.toThrowError(
expect.objectContaining({
code: 'token_for_connection_error',
message:
'Either a refresh or access token must be specified.'
})
);
});

test('getTokenForConnection - should throw when token exchange failed', async () => {
const authClient = new AuthClient({
domain,
Expand Down
36 changes: 34 additions & 2 deletions packages/auth0-auth-js/src/auth-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ const GRANT_TYPE_FEDERATED_CONNECTION_ACCESS_TOKEN =
const SUBJECT_TYPE_REFRESH_TOKEN =
'urn:ietf:params:oauth:token-type:refresh_token';

/**
* Constant representing the subject type for an access token.
* This is used in OAuth 2.0 token exchange to specify that the token being exchanged is an access token.
*
* @see {@link https://tools.ietf.org/html/rfc8693#section-3.1 RFC 8693 Section 3.1}
*/
const SUBJECT_TYPE_ACCESS_TOKEN =
'urn:ietf:params:oauth:token-type:access_token';

/**
* A constant representing the token type for federated connection access tokens.
* This is used to specify the type of token being requested from Auth0.
Expand Down Expand Up @@ -260,13 +269,36 @@ export class AuthClient {
public async getTokenForConnection(
options: TokenForConnectionOptions
): Promise<TokenResponse> {
if (options.refreshToken && options.accessToken) {
throw new TokenForConnectionError(
'Either a refresh or access token should be specified, but not both.'
);
}

let subjectTokenType = null;
let token = null;

if (options.refreshToken) {
subjectTokenType = SUBJECT_TYPE_REFRESH_TOKEN;
token = options.refreshToken;
} else if (options.accessToken) {
subjectTokenType = SUBJECT_TYPE_ACCESS_TOKEN;
token = options.accessToken;
}

if (!token || !subjectTokenType) {
throw new TokenForConnectionError(
'Either a refresh or access token must be specified.'
);
}

const { configuration } = await this.#discover();

const params = new URLSearchParams();

params.append('connection', options.connection);
params.append('subject_token_type', SUBJECT_TYPE_REFRESH_TOKEN);
params.append('subject_token', options.refreshToken);
params.append('subject_token_type', subjectTokenType);
params.append('subject_token', token);
params.append(
'requested_token_type',
REQUESTED_TOKEN_TYPE_FEDERATED_CONNECTION_ACCESS_TOKEN
Expand Down
6 changes: 5 additions & 1 deletion packages/auth0-auth-js/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,11 @@ export interface TokenForConnectionOptions {
/**
* The refresh token to use to get an access token for the connection.
*/
refreshToken: string;
refreshToken?: string;
/**
* The access token to use to get an access token for the connection.
*/
accessToken?: string;
}

export interface BuildLogoutUrlOptions {
Expand Down