Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
31 changes: 31 additions & 0 deletions Examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
- [4.2. Client Initiated Backchannel Authorization (CIBA) with authorization details](#42-client-initiated-backchannel-authorization-ciba-with-authorization-details)
- [5. Multi-Resource Refresh Token (MRRT)](#5-multi-resource-refresh-token-mrrt)
- [6. Custom Token Exchange (CTE)](#6-custom-token-exchange-cte)
- [7. Token Vault (Federated Connection Access Token)](#7-token-vault-federated-connection-access-token)

## 1. Client Initialization

Expand Down Expand Up @@ -359,6 +360,36 @@ if (sttResponse.IssuedTokenType == TokenType.SessionTransferToken)

[Go to Top](#)

## 7. Token Vault (Federated Connection Access Token)

Token Vault lets you exchange an Auth0 Access Token / Refresh Token for an access token issued by one of
your federated connections (Google, Facebook, etc.), so your app can call that provider's
APIs on the user's behalf. The client must be a private client.

```csharp
using Auth0.AuthenticationApi;
using Auth0.AuthenticationApi.Models;

var auth = new AuthenticationApiClient("YOUR_AUTH0_DOMAIN");

var tokenResponse = await auth.GetTokenAsync(new FederatedConnectionAccessTokenRequest
{
ClientId = "YOUR_CLIENT_ID",
ClientSecret = "YOUR_CLIENT_SECRET",
SubjectToken = "THE_AUTH0_ACCESS_TOKEN",
SubjectTokenType = TokenType.AccessToken,
Connection = "google-oauth2",
LoginHint = "THE_GOOGLE_USER_ID" // optional
});

Console.WriteLine($"Federated Access Token: {tokenResponse.AccessToken}");
```

> `Connection` is required — omitting it throws `ArgumentException` before any network
> call. `requested_token_type` is fixed to the federated-connection URN and set for you.

[Go to Top](#)

# Management API

- [1. Management Client Initialization](#1-management-client-initialization)
Expand Down
35 changes: 35 additions & 0 deletions src/Auth0.AuthenticationApi/AuthenticationApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
/// <code>var client = new AuthenticationApiClient(baseUri, new HttpClientAuthenticationConnection(myHttpClient));</code> or
/// <code>var client = new AuthenticationApiClient(baseUri, new HttpClientAuthenticationConnection(myHttpMessageHandler));</code> or
/// </remarks>
public AuthenticationApiClient(Uri baseUri, IAuthenticationConnection connection = null)

Check warning on line 47 in src/Auth0.AuthenticationApi/AuthenticationApiClient.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.
{
BaseUri = baseUri;
if (connection == null)
Expand Down Expand Up @@ -74,7 +74,7 @@
/// <code>var client = new AuthenticationApiClient(domain, new HttpClientAuthenticationConnection(myHttpClient));</code> or
/// <code>var client = new AuthenticationApiClient(domain, new HttpClientAuthenticationConnection(myHttpMessageHandler));</code> or
/// </remarks>
public AuthenticationApiClient(string domain, IAuthenticationConnection connection = null)

Check warning on line 77 in src/Auth0.AuthenticationApi/AuthenticationApiClient.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.
: this(new Uri($"https://{domain}"), connection)
{
}
Expand Down Expand Up @@ -270,6 +270,41 @@
return response;
}

/// <inheritdoc/>
public async Task<AccessTokenResponse> GetTokenAsync(FederatedConnectionAccessTokenRequest request, CancellationToken cancellationToken = default)
{
request.ThrowIfNull();

if (string.IsNullOrEmpty(request.Connection))
throw new ArgumentException(
"Connection is required for a federated connection access token exchange.",
nameof(request.Connection));

var body = new Dictionary<string, string> {
{ "grant_type", "urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token" },
{ "client_id", request.ClientId },
{ "subject_token", request.SubjectToken },
{ "subject_token_type", request.SubjectTokenType },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SubjectToken and SubjectTokenType are both required but we add them to the body with no check. We already throw a clear ArgumentException for a missing Connection a few lines up, so it feels odd to let these two through and get back an opaque 400 from the server.

Can we add the same kind of early null or empty guard for SubjectToken and SubjectTokenType?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 5b53c29
Added validation

{ "requested_token_type", TokenType.FederatedConnectionAccessToken },
{ "connection", request.Connection }
};

ApplyClientAuthentication(request, body);

body.AddIfNotEmpty("login_hint", request.LoginHint);

var response = await connection.SendAsync<AccessTokenResponse>(
HttpMethod.Post,
tokenUri,
body,
cancellationToken: cancellationToken
).ConfigureAwait(false);

await AssertIdTokenValidIfExisting(response.IdToken, request.ClientId, request.SigningAlgorithm, request.ClientSecret, null, request.Nonce).ConfigureAwait(false);

return response;
}

/// <inheritdoc/>
public async Task<AccessTokenResponse> GetTokenAsync(ResourceOwnerTokenRequest request, CancellationToken cancellationToken = default)
{
Expand Down
11 changes: 11 additions & 0 deletions src/Auth0.AuthenticationApi/IAuthenticationApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,17 @@ public interface IAuthenticationApiClient : IDisposable
/// <see cref="AccessTokenResponse.IssuedTokenType"/> to determine the kind of token issued.</returns>
Task<AccessTokenResponse> GetTokenAsync(TokenExchangeTokenRequest request, CancellationToken cancellationToken = default);

/// <summary>
/// Exchanges an Auth0 token for an access token issued by a federated connection
/// (Token Vault), using the Auth0 federated-connection-access-token grant.
/// </summary>
/// <param name="request"><see cref="FederatedConnectionAccessTokenRequest"/> containing the subject token, connection, and associated parameters.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns><see cref="Task"/> representing the async operation containing
/// a <see cref="AccessTokenResponse" />. The federated connection access token is
/// returned in <see cref="TokenBase.AccessToken"/>.</returns>
Task<AccessTokenResponse> GetTokenAsync(FederatedConnectionAccessTokenRequest request, CancellationToken cancellationToken = default);

/// <summary>
/// Performs authentication by providing user-supplied information in a <see cref="ResourceOwnerTokenRequest"/>.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using Microsoft.IdentityModel.Tokens;

namespace Auth0.AuthenticationApi.Models;

/// <summary>
/// Represents a request to exchange an Auth0 token for an access token issued by one of
/// Auth0's federated connections (Token Vault), using the Auth0 token-exchange grant
/// <c>urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token</c>.
/// </summary>
/// <remarks>
/// The client must be a private client. Today the subject token must be an Auth0 refresh
/// token (<see cref="TokenType.RefreshToken"/>); support for access-token subjects is
/// planned by Auth0.
/// </remarks>
public class FederatedConnectionAccessTokenRequest : IClientAuthentication
{
/// <summary>
/// The Auth0 token being exchanged. Currently this must be a valid Auth0 refresh token.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doc says the subject token must be an Auth0 refresh token, but the example, every test in this PR, and the Token Vault docs all use an access token (subject_token_type = urn:ietf:params:oauth:token-type:access_token). The Auth0 docs describe subject_token as the Auth0 access token that identifies the user.

The class remark on line 11 and the SubjectTokenType note on line 25 say the same refresh token thing, so all three spots contradict what the code actually does. Since this text shows up in IntelliSense, a user following it would pass a refresh token and get a 400. Can we update these to say access token (TokenType.AccessToken)? If refresh tokens are also valid, then let us word it as access or refresh token, but right now nothing exercises the refresh token path.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 5b53c29
subject_token_type can be both AccessToken or RefreshToken. Updated the code docs to reflect the same.

/// Required.
/// </summary>
public string SubjectToken { get; set; }

/// <summary>
/// An identifier that indicates the type of <see cref="SubjectToken"/>, as a URN.
/// Currently only <see cref="TokenType.RefreshToken"/> is accepted. Required.
/// </summary>
public string SubjectTokenType { get; set; }

/// <summary>
/// The federated connection to obtain the access token for (for example
/// <c>google-oauth2</c>). Required.
/// </summary>
public string Connection { get; set; }

/// <summary>
/// Optional user ID of the current user within the IdP named by <see cref="Connection"/>.
/// For example, if the connection is <c>google-oauth2</c>, this is the Google user ID.
/// </summary>
public string LoginHint { get; set; }

/// <summary>
/// Client ID of the application.
/// </summary>
public string ClientId { get; set; }

/// <summary>
/// Client Secret of the application.
/// </summary>
public string ClientSecret { get; set; }

/// <summary>
/// Security Key to use with Client Assertion.
/// </summary>
public SecurityKey ClientAssertionSecurityKey { get; set; }

/// <summary>
/// Algorithm for the Security Key to use with Client Assertion.
/// </summary>
public string ClientAssertionSecurityKeyAlgorithm { get; set; }

/// <summary>
/// What <see cref="JwtSignatureAlgorithm"/> is used to verify the signature of Id Tokens.
/// </summary>
public JwtSignatureAlgorithm SigningAlgorithm { get; set; }

/// <summary>
/// Optional nonce to validate against the nonce claim in a returned ID token.
/// </summary>
/// <remarks>
/// When set, the nonce claim in the returned ID token must exactly match this value.
/// Leave null (the default) to skip nonce validation.
/// </remarks>
public string? Nonce { get; set; }
}
10 changes: 10 additions & 0 deletions src/Auth0.AuthenticationApi/Models/TokenType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,14 @@ public static class TokenType
/// Indicates that the token is an Auth0 Session Transfer Token: <c>urn:auth0:params:oauth:token-type:session_transfer_token</c>.
/// </summary>
public const string SessionTransferToken = "urn:auth0:params:oauth:token-type:session_transfer_token";

/// <summary>
/// Indicates that the token is an OAuth 2.0 refresh token: <c>urn:ietf:params:oauth:token-type:refresh_token</c>.
/// </summary>
public const string RefreshToken = "urn:ietf:params:oauth:token-type:refresh_token";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This RefreshToken constant does not seem to be used anywhere in the feature. All the code paths and tests use AccessToken. It looks like it was added only to support the refresh token wording in the request model, which I think is not correct (left a note there).

If refresh token subjects are genuinely supported, can we add a test that uses this? Otherwise I would drop it so we do not signal a path that does not work. Low priority either way.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 5b53c29
This is a public property. Also, now we have added test cases that use this RefreshToken as well.


/// <summary>
/// Indicates that the token is an Auth0 federated connection access token: <c>http://auth0.com/oauth/token-type/federated-connection-access-token</c>.
/// </summary>
public const string FederatedConnectionAccessToken = "http://auth0.com/oauth/token-type/federated-connection-access-token";
}
Loading
Loading