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
45 changes: 45 additions & 0 deletions packages/auth0-auth-js/EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- [Configuration](#configuration)
- [Configuring the Scopes](#configuring-the-scopes)
- [Configuring PrivateKeyJwt](#configuring-privatekeyjwt)
- [Configuring mTLS (Mutual TLS)](#configuring-mtls-mutual-tls)
- [Configuring the `authorizationParams` globally](#configuring-the-authorizationparams-globally)
- [Configuring a `customFetch` implementation](#configuring-a-customfetch-implementation)
- [Building the Authorization URL](#building-the-authorization-url)
Expand Down Expand Up @@ -59,6 +60,50 @@ const auth0 = new AuthClient({

Note that the private keys should not be committed to source control, and should be stored securely.

### Configuring mTLS (Mutual TLS)

The SDK supports mTLS (Mutual TLS) authentication, which provides stronger security by using client certificates for authentication. When using mTLS, you don't need to provide a client secret or private key JWT since the client certificate serves as the authentication mechanism.

To use mTLS, set `useMtls: true` and provide a `customFetch` implementation that includes your client certificate:

```ts
import { AuthClient } from '@auth0/auth0-auth-js';
import { Agent } from 'undici';

const auth0 = new AuthClient({
domain: '<AUTH0_DOMAIN>',
clientId: '<AUTH0_CLIENT_ID>',
useMtls: true,
customFetch: (url, options) => {
return fetch(url, {
...options,
dispatcher: new Agent({
connect: {
key: '...',
cert: '...',
ca: '...',
},
}),
});
},
});

// Example: Get a token using client credentials with mTLS
const tokenResponse = await auth0.getTokenByClientCredentials({
audience: 'https://your-api.example.com',
});
```

**Key points for mTLS configuration:**

- **Client Certificate**: Your application must have a valid client certificate issued by a Certificate Authority (CA) that Auth0 trusts.
- **Domain Configuration**: Your Auth0 tenant must be configured to support mTLS endpoints.
- **No Additional Auth**: When `useMtls: true`, you don't need `clientSecret` or `clientAssertionSigningKey`.
- **Custom Fetch Required**: You must provide a `customFetch` implementation that includes the client certificate in the TLS handshake.

> [!IMPORTANT]
> mTLS requires proper certificate management and Auth0 tenant configuration. Make sure your Auth0 tenant supports mTLS endpoints and that your client certificates are properly configured in the Auth0 Dashboard. Learn how to configure mTLS in your Auth0 tenant by reading the [mTLS configuration documentation](https://auth0.com/docs/get-started/applications/configure-mtls).

### Configuring the `authorizationParams` globally

The `authorizationParams` object can be used to customize the authorization parameters that will be passed to the `/authorize` endpoint. This object can be passed when creating an instance of `AuthClient`, but it can also be specified when calling certain methods of the SDK, for example `buildAuthorizationUrl`. For each of these, the same rule applies in the sense that both `authorizationParams` objects will be merged, where those provided to the method, override those provided when creating the instance.
Expand Down
73 changes: 73 additions & 0 deletions packages/auth0-auth-js/src/auth-client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { pemToArrayBuffer } from './test-utils/pem.js';

const domain = 'auth0.local';
let accessToken: string;
let mtlsAccessToken: string;
let accessTokenWithAudienceAndBindingMessage: string;
let mockOpenIdConfiguration = {
issuer: `https://${domain}/`,
Expand All @@ -25,6 +26,12 @@ let mockOpenIdConfiguration = {
end_session_endpoint: `https://${domain}/logout`,
pushed_authorization_request_endpoint: `https://${domain}/pushed-authorize`,
jwks_uri: `https://${domain}/.well-known/jwks.json`,
mtls_endpoint_aliases: {
token_endpoint: `https://mtls.${domain}/oauth/token`,
userinfo_endpoint: `https://mtls.${domain}/userinfo`,
revocation_endpoint: `https://mtls.${domain}/oauth/revoke`,
pushed_authorization_request_endpoint: `https://mtls.${domain}/oauth/par`,
},
};

const restHandlers = [
Expand Down Expand Up @@ -95,6 +102,19 @@ const restHandlers = [
});
}),

http.post(
mockOpenIdConfiguration.mtls_endpoint_aliases.token_endpoint,
async () => {
return HttpResponse.json({
access_token: mtlsAccessToken,
id_token: await generateToken(domain, 'user_123', '<client_id>'),
expires_in: 60,
token_type: 'Bearer',
scope: '<scope>',
});
}
),

http.post(
mockOpenIdConfiguration.pushed_authorization_request_endpoint,
async ({ request }) => {
Expand Down Expand Up @@ -125,6 +145,7 @@ afterAll(() => server.close());

beforeEach(async () => {
accessToken = await generateToken(domain, 'user_123');
mtlsAccessToken = await generateToken(domain, 'user_abc');
accessTokenWithAudienceAndBindingMessage = await generateToken(
domain,
'user_789'
Expand All @@ -140,6 +161,12 @@ afterEach(() => {
end_session_endpoint: `https://${domain}/logout`,
pushed_authorization_request_endpoint: `https://${domain}/pushed-authorize`,
jwks_uri: `https://${domain}/.well-known/jwks.json`,
mtls_endpoint_aliases: {
token_endpoint: `https://mtls.${domain}/oauth/token`,
userinfo_endpoint: `https://mtls.${domain}/userinfo`,
revocation_endpoint: `https://mtls.${domain}/oauth/revoke`,
pushed_authorization_request_endpoint: `https://mtls.${domain}/oauth/par`,
},
};
server.resetHandlers();
});
Expand Down Expand Up @@ -301,6 +328,52 @@ test('configuration - should throw when no key configured', async () => {
await expect(authClient.buildAuthorizationUrl()).rejects.toThrowError('The client secret or client assertion signing key must be provided.');
});

test('configuration - should use mTLS when useMtls is true', async () => {
const authClient = new AuthClient({
domain,
clientId: '<client_id>',
useMtls: true,
// For mTLS to actually work in an actual application,
// a custom fetch implementation should be provided, containing the corresponding configuration for mTLS.
customFetch: fetch,
});

const tokenResponse = await authClient.getTokenByCode(
new URL(`https://${domain}?code=123`),
{
codeVerifier: '123',
}
);

expect(tokenResponse.accessToken).toBe(mtlsAccessToken);
});

test('configuration - should use mTLS when useMtls is true but no aliases', async () => {
// @ts-expect-error Ignore the fact that this property is not defined as optional in the test.
delete mockOpenIdConfiguration.mtls_endpoint_aliases;

const authClient = new AuthClient({
domain,
clientId: '<client_id>',
useMtls: true,
// For mTLS to actually work in an actual application,
// a custom fetch implementation should be provided, containing the corresponding configuration for mTLS.
customFetch: fetch,
});

const tokenResponse = await authClient.getTokenByCode(
new URL(`https://${domain}?code=123`),
{
codeVerifier: '123',
}
);

// When no aliases, we will end up calling the regular oauth/token endpoint,
// and not the mTLS alias.
// We know that in the case of our tests, that means it returns an `accessToken` instead of `mtlsAccessToken`.
expect(tokenResponse.accessToken).toBe(accessToken);
});

test('buildAuthorizationUrl - should throw when using PAR without PAR support', async () => {
const serverClient = new AuthClient({
domain,
Expand Down
11 changes: 8 additions & 3 deletions packages/auth0-auth-js/src/auth-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,10 @@ export class AuthClient {
this.#configuration = await client.discovery(
new URL(`https://${this.#options.domain}`),
this.#options.clientId,
{},
{ use_mtls_endpoint_aliases: this.#options.useMtls },
clientAuth,
{
[client.customFetch]: this.#options.customFetch,
[client.customFetch]: this.#options.customFetch,
}
);

Expand Down Expand Up @@ -492,11 +492,16 @@ export class AuthClient {
async #getClientAuth(): Promise<client.ClientAuth> {
if (
!this.#options.clientSecret &&
!this.#options.clientAssertionSigningKey
!this.#options.clientAssertionSigningKey &&
!this.#options.useMtls
) {
throw new MissingClientAuthError();
}

if (this.#options.useMtls) {
return client.TlsClientAuth();
}

let clientPrivateKey = this.#options.clientAssertionSigningKey as
| CryptoKey
| undefined;
Expand Down
5 changes: 5 additions & 0 deletions packages/auth0-auth-js/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ export interface AuthClientOptions {
* Optional, custom Fetch implementation to use.
*/
customFetch?: typeof fetch;

/**
* Indicates whether the SDK should use the mTLS endpoints if they are available.
*/
useMtls?: boolean;
}

export interface AuthorizationParameters {
Expand Down