Skip to content
Open
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
52 changes: 52 additions & 0 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,58 @@ Tokens tokens = controller.loginWithCustomTokenExchange("EXTERNAL-SUBJECT-TOKEN"
.execute();
```

## Token Vault (Federated Connection Access Tokens)

[Token Vault](https://auth0.com/docs/secure/tokens/token-vault) exchanges an Auth0 token for an external identity provider's access token (for example Google, GitHub, or Slack), so your application can call that provider's API on the user's behalf. The returned access token is the external provider's token — opaque to your application — and the grant returns no ID token and no refresh token.

The library remains stateless: your application owns storage of the subject token, caching of the resulting connection access token, and any concurrency control.

The subject token can be an Auth0 refresh token or access token. Use the typed factory method that matches what you hold; a generic overload accepts a caller-supplied subject-token-type for other cases.

```java
// Exchange a refresh token for the connection's access token.
Tokens tokens = controller.getTokenForConnectionWithRefreshToken("google-oauth2", "YOUR-REFRESH-TOKEN")
.execute();

// Or exchange an access token.
Tokens tokens = controller.getTokenForConnectionWithAccessToken("google-oauth2", "YOUR-ACCESS-TOKEN")
.execute();

// The access token is the external provider's token; the ID and refresh tokens are null.
String connectionAccessToken = tokens.getAccessToken();
```

Optionally set a `login_hint` — the user's ID within the identity provider (for example, the Google user ID when the connection is `google-oauth2`):

```java
Tokens tokens = controller.getTokenForConnectionWithRefreshToken("google-oauth2", "YOUR-REFRESH-TOKEN")
.withLoginHint("google-user-id")
.execute();
```

For subject-token-types other than the refresh-token and access-token variants, use the generic overload and pass the subject-token-type URN explicitly:

```java
Tokens tokens = controller.getTokenForConnection("google-oauth2", "YOUR-SUBJECT-TOKEN", "urn:ietf:params:oauth:token-type:refresh_token")
.execute();
```

### Using Token Vault with Multiple Custom Domains

A connection exchange is bound to the domain the subject token was issued for at login. When using a `DomainResolver`, pass that domain explicitly — supply the value stored from `Tokens.getDomain()` at login. This is required because a connection exchange can occur outside of an HTTP request:

```java
Tokens tokens = controller.getTokenForConnectionWithRefreshToken("google-oauth2", "YOUR-REFRESH-TOKEN", "acme.auth0.com")
.execute();
```

Alternatively, pass the `HttpServletRequest` to let the resolver derive the domain. Note that if the resolver resolves the request to a different domain than the one the subject token was issued for, Auth0 will reject the grant:

```java
Tokens tokens = controller.getTokenForConnectionWithRefreshToken("google-oauth2", "YOUR-REFRESH-TOKEN", request)
.execute();
```

## Client-Initiated Backchannel Authentication (CIBA)

[CIBA](https://auth0.com/docs/get-started/authentication-and-authorization-flow/client-initiated-backchannel-authentication-flow) is a decoupled flow: the application initiates authentication and the user approves it out-of-band on a separate device (e.g., a push notification to their phone). It is a **two-step** flow:
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ Beyond the interactive login flow above, the `AuthenticationController` supports
- **Refresh Token Grant (MRRT)** — exchange a refresh token for fresh tokens, including Multi-Resource Refresh Token flows that target multiple APIs. See [EXAMPLES.md](./EXAMPLES.md#refresh-token-grant-mrrt).
- **Custom Token Exchange (CTE)** — exchange an external `subject_token` for Auth0 tokens (RFC 8693). See [EXAMPLES.md](./EXAMPLES.md#custom-token-exchange-cte).
- **Client-Initiated Backchannel Authentication (CIBA)** — a decoupled flow where the user approves authentication out-of-band on a separate device. See [EXAMPLES.md](./EXAMPLES.md#client-initiated-backchannel-authentication-ciba).
- **Token Vault (Federated Connection Access Tokens)** — exchange an Auth0 token for an external identity provider's access token to call that provider's API on the user's behalf. See [EXAMPLES.md](./EXAMPLES.md#token-vault-federated-connection-access-tokens).

## API Reference

Expand Down
181 changes: 181 additions & 0 deletions src/main/java/com/auth0/AuthenticationController.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
return new Builder(clientId, clientSecret).withDomainResolver(domainResolver);
}

public static class Builder {

Check warning on line 70 in src/main/java/com/auth0/AuthenticationController.java

View workflow job for this annotation

GitHub Actions / gradle

no comment
private static final String RESPONSE_TYPE_CODE = "code";

private String domain;
Expand Down Expand Up @@ -615,4 +615,185 @@
return requestProcessor.buildTokenExchangeRequest(subjectToken, subjectTokenType, true);
}

/**
* Builds a request to exchange an Auth0 refresh token for an external identity provider's
* access token via <a href="https://auth0.com/docs/secure/tokens/token-vault">Token Vault</a>,
* using the statically configured domain.
*
* <p>This overload is only valid when the controller was configured with a fixed domain. When a
* {@code DomainResolver} is in use, call
* {@link #getTokenForConnectionWithRefreshToken(String, String, String)} with the domain.</p>
*
* @param connection the federated connection name (e.g. {@code google-oauth2}).
* @param refreshToken the Auth0 refresh token to exchange.
* @return a {@link ConnectionTokenRequest} to configure and execute.
* @throws IllegalStateException if the controller was configured with a {@code DomainResolver}.
*/
public ConnectionTokenRequest getTokenForConnectionWithRefreshToken(String connection, String refreshToken) {
Validate.notNull(connection, "connection must not be null");
Validate.notNull(refreshToken, "refreshToken must not be null");
return requestProcessor.buildConnectionTokenRequest(connection, refreshToken, "urn:ietf:params:oauth:token-type:refresh_token");
}

/**
* Builds a Token Vault request exchanging an Auth0 refresh token against the given domain.
*
* <p>A connection exchange is bound to the domain the subject token was issued for at login;
* supply the domain stored from {@link Tokens#getDomain()} at login. This overload is required
* in Multiple Custom Domains (MCD) setups, and works outside of an HTTP request.</p>
*
* @param connection the federated connection name.
* @param refreshToken the Auth0 refresh token to exchange.
* @param domain the Auth0 domain to target.
* @return a {@link ConnectionTokenRequest} to configure and execute.
*/
public ConnectionTokenRequest getTokenForConnectionWithRefreshToken(String connection, String refreshToken, String domain) {
Validate.notNull(connection, "connection must not be null");
Validate.notNull(refreshToken, "refreshToken must not be null");
Validate.notNull(domain, "domain must not be null");
return requestProcessor.buildConnectionTokenRequest(connection, refreshToken, "urn:ietf:params:oauth:token-type:refresh_token", domain);
}

/**
* Builds a Token Vault request exchanging an Auth0 refresh token, resolving the domain from the
* given request via the configured domain or {@code DomainResolver}.
*
* <p><strong>Note:</strong> a connection exchange is bound to the domain the subject token was
* issued for at login; if the resolver resolves this request to a different domain, Auth0 will
* reject the grant. In MCD setups prefer
* {@link #getTokenForConnectionWithRefreshToken(String, String, String)} with the domain stored
* from {@link Tokens#getDomain()} at login.</p>
*
* @param connection the federated connection name.
* @param refreshToken the Auth0 refresh token to exchange.
* @param request the current HTTP request, used to resolve the domain.
* @return a {@link ConnectionTokenRequest} to configure and execute.
*/
public ConnectionTokenRequest getTokenForConnectionWithRefreshToken(String connection, String refreshToken, HttpServletRequest request) {
Validate.notNull(connection, "connection must not be null");
Validate.notNull(refreshToken, "refreshToken must not be null");
Validate.notNull(request, "request must not be null");
return requestProcessor.buildConnectionTokenRequest(connection, refreshToken, "urn:ietf:params:oauth:token-type:refresh_token", request);
}

/**
* Builds a request to exchange an Auth0 access token for an external identity provider's access
* token via <a href="https://auth0.com/docs/secure/tokens/token-vault">Token Vault</a>, using
* the statically configured domain.
*
* <p>This overload is only valid when the controller was configured with a fixed domain. When a
* {@code DomainResolver} is in use, call
* {@link #getTokenForConnectionWithAccessToken(String, String, String)} with the domain.</p>
*
* @param connection the federated connection name (e.g. {@code google-oauth2}).
* @param accessToken the Auth0 access token to exchange.
* @return a {@link ConnectionTokenRequest} to configure and execute.
* @throws IllegalStateException if the controller was configured with a {@code DomainResolver}.
*/
public ConnectionTokenRequest getTokenForConnectionWithAccessToken(String connection, String accessToken) {
Validate.notNull(connection, "connection must not be null");
Validate.notNull(accessToken, "accessToken must not be null");
return requestProcessor.buildConnectionTokenRequest(connection, accessToken, "urn:ietf:params:oauth:token-type:access_token");
}

/**
* Builds a Token Vault request exchanging an Auth0 access token against the given domain.
*
* <p>A connection exchange is bound to the domain the subject token was issued for at login;
* supply the domain stored from {@link Tokens#getDomain()} at login. This overload is required
* in Multiple Custom Domains (MCD) setups.</p>
*
* @param connection the federated connection name.
* @param accessToken the Auth0 access token to exchange.
* @param domain the Auth0 domain to target.
* @return a {@link ConnectionTokenRequest} to configure and execute.
*/
public ConnectionTokenRequest getTokenForConnectionWithAccessToken(String connection, String accessToken, String domain) {
Validate.notNull(connection, "connection must not be null");
Validate.notNull(accessToken, "accessToken must not be null");
Validate.notNull(domain, "domain must not be null");
return requestProcessor.buildConnectionTokenRequest(connection, accessToken, "urn:ietf:params:oauth:token-type:access_token", domain);
}

/**
* Builds a Token Vault request exchanging an Auth0 access token, resolving the domain from the
* given request via the configured domain or {@code DomainResolver}.
*
* <p><strong>Note:</strong> a connection exchange is bound to the domain the subject token was
* issued for at login; if the resolver resolves this request to a different domain, Auth0 will
* reject the grant. In MCD setups prefer
* {@link #getTokenForConnectionWithAccessToken(String, String, String)} with the domain stored
* from {@link Tokens#getDomain()} at login.</p>
*
* @param connection the federated connection name.
* @param accessToken the Auth0 access token to exchange.
* @param request the current HTTP request, used to resolve the domain.
* @return a {@link ConnectionTokenRequest} to configure and execute.
*/
public ConnectionTokenRequest getTokenForConnectionWithAccessToken(String connection, String accessToken, HttpServletRequest request) {
Validate.notNull(connection, "connection must not be null");
Validate.notNull(accessToken, "accessToken must not be null");
Validate.notNull(request, "request must not be null");
return requestProcessor.buildConnectionTokenRequest(connection, accessToken, "urn:ietf:params:oauth:token-type:access_token", request);
}

/**
* Builds a Token Vault request with a caller-supplied subject-token-type, using the statically
* configured domain. This is the generic escape hatch for subject-token-types other than the
* refresh-token and access-token variants covered by the typed methods.
*
* <p>This overload is only valid when the controller was configured with a fixed domain. When a
* {@code DomainResolver} is in use, call
* {@link #getTokenForConnection(String, String, String, String)} with the domain.</p>
*
* @param connection the federated connection name (e.g. {@code google-oauth2}).
* @param subjectToken the Auth0 token to exchange.
* @param subjectTokenType the subject-token-type URN describing {@code subjectToken}.
* @return a {@link ConnectionTokenRequest} to configure and execute.
* @throws IllegalStateException if the controller was configured with a {@code DomainResolver}.
*/
public ConnectionTokenRequest getTokenForConnection(String connection, String subjectToken, String subjectTokenType) {
Validate.notNull(connection, "connection must not be null");
Validate.notNull(subjectToken, "subjectToken must not be null");
Validate.notNull(subjectTokenType, "subjectTokenType must not be null");
return requestProcessor.buildConnectionTokenRequest(connection, subjectToken, subjectTokenType);
}

/**
* Builds a Token Vault request with a caller-supplied subject-token-type against the given
* domain. See {@link #getTokenForConnection(String, String, String)} for details.
*
* @param connection the federated connection name.
* @param subjectToken the Auth0 token to exchange.
* @param subjectTokenType the subject-token-type URN describing {@code subjectToken}.
* @param domain the Auth0 domain to target.
* @return a {@link ConnectionTokenRequest} to configure and execute.
*/
public ConnectionTokenRequest getTokenForConnection(String connection, String subjectToken, String subjectTokenType, String domain) {
Validate.notNull(connection, "connection must not be null");
Validate.notNull(subjectToken, "subjectToken must not be null");
Validate.notNull(subjectTokenType, "subjectTokenType must not be null");
Validate.notNull(domain, "domain must not be null");
return requestProcessor.buildConnectionTokenRequest(connection, subjectToken, subjectTokenType, domain);
}

/**
* Builds a Token Vault request with a caller-supplied subject-token-type, resolving the domain
* from the given request. See {@link #getTokenForConnection(String, String, String)} for
* details and the domain-binding caveat.
*
* @param connection the federated connection name.
* @param subjectToken the Auth0 token to exchange.
* @param subjectTokenType the subject-token-type URN describing {@code subjectToken}.
* @param request the current HTTP request, used to resolve the domain.
* @return a {@link ConnectionTokenRequest} to configure and execute.
*/
public ConnectionTokenRequest getTokenForConnection(String connection, String subjectToken, String subjectTokenType, HttpServletRequest request) {
Validate.notNull(connection, "connection must not be null");
Validate.notNull(subjectToken, "subjectToken must not be null");
Validate.notNull(subjectTokenType, "subjectTokenType must not be null");
Validate.notNull(request, "request must not be null");
return requestProcessor.buildConnectionTokenRequest(connection, subjectToken, subjectTokenType, request);
}

}
77 changes: 77 additions & 0 deletions src/main/java/com/auth0/ConnectionTokenRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.auth0;

import com.auth0.client.auth.AuthAPI;
import com.auth0.exception.Auth0Exception;
import com.auth0.json.auth.TokenHolder;
import com.auth0.net.TokenRequest;

/**
* Class to exchange an Auth0 token for an external identity provider's access token via
* <a href="https://auth0.com/docs/secure/tokens/token-vault">Token Vault</a> (the
* federated-connection access-token grant). The returned access token is the external provider's
* token (for example Google, GitHub, or Slack), opaque to this application, suitable for calling
* that provider's API on the user's behalf.
* <p>
* The subject token may be an Auth0 refresh token or access token; the subject-token-type is fixed
* by the factory method used to create this request. The federated-connection grant returns an
* access token, {@code scope}, {@code expires_in}, and {@code token_type} — no ID token and no
* refresh token.
* <p>
* The library remains stateless: the application owns storage of the subject token, caching of the
* resulting connection access token, and any concurrency control.
* <p>
* Obtain an instance via one of the {@code AuthenticationController} factory methods:
* {@link AuthenticationController#getTokenForConnectionWithRefreshToken(String, String)},
* {@link AuthenticationController#getTokenForConnectionWithAccessToken(String, String)}, or
* {@link AuthenticationController#getTokenForConnection(String, String, String)} (and their
* domain / request overloads).
*/
@SuppressWarnings({"UnusedReturnValue", "WeakerAccess", "unused"})
public class ConnectionTokenRequest {

private final AuthAPI client;
private final String connection;
private final String subjectToken;
private final String subjectTokenType;
private final String domain;
private final String issuer;
private String loginHint;

ConnectionTokenRequest(AuthAPI client, String connection, String subjectToken,
String subjectTokenType, String domain, String issuer) {
this.client = client;
this.connection = connection;
this.subjectToken = subjectToken;
this.subjectTokenType = subjectTokenType;
this.domain = domain;
this.issuer = issuer;
}

/**
* Sets the {@code login_hint} — the user's ID within the identity provider specified by the
* connection (for example, the Google user ID when the connection is {@code google-oauth2}).
* When not set, no {@code login_hint} is sent.
*
* @param loginHint the provider-side identity provider user ID.
* @return this request instance for fluent chaining.
*/
public ConnectionTokenRequest withLoginHint(String loginHint) {
this.loginHint = loginHint;
return this;
}

/**
* Executes the federated-connection token exchange against Auth0 and returns the resulting
* tokens. The returned {@link Tokens#getAccessToken()} is the external provider's access token;
* {@link Tokens#getIdToken()} and {@link Tokens#getRefreshToken()} are null.
*
* @return the {@link Tokens} obtained from the grant.
* @throws Auth0Exception if the request to the Auth0 server failed.
*/
public Tokens execute() throws Auth0Exception {
TokenRequest request = client.getTokenForConnection(connection, subjectToken, subjectTokenType, loginHint);
TokenHolder holder = request.execute().getBody();
return new Tokens(holder.getAccessToken(), null, null,
holder.getTokenType(), holder.getExpiresIn(), holder.getScope(), domain, issuer);
}
}
Loading
Loading