- 1. Client Initialization
- 2. Login With Client Credentials
- 3. Authenticate using Resource Owner Password Grant Flow with MFA
- 4. Rich Authorization Requests (RAR)
- 5. Multi-Resource Refresh Token (MRRT)
- 6. Custom Token Exchange (CTE)
- 7. Token Vault (Federated Connection Access Token)
- 8. On-Behalf-Of Token Exchange (OBO)
public async Task Initialize(string returnUrl = "/")
{
var authClient = new AuthenticationApiClient("my.custom.domain");
}Or you could pass your own implementation of the IAuthenticationConnection to override the default behaviour of interacting with the Auth0 server as below.
public class ExampleApp
{
public class YourCustomImplementation : IAuthenticationConnection
{
public Task<T> GetAsync<T>(
Uri uri,
IDictionary<string, string> headers = null,
CancellationToken cancellationToken = default)
{
// Custom Implementation
return (Task<T>)Task.CompletedTask;
}
public Task<T> SendAsync<T>(
HttpMethod method,
Uri uri,
object body,
IDictionary<string, string> headers = null,
CancellationToken cancellationToken = default)
{
// Custom Implementation
return (Task<T>)Task.CompletedTask;
}
}
public async Task Initialize(string returnUrl = "/")
{
var authConnection = new YourCustomImplementation();
var authClient = new AuthenticationApiClient("my.custom.domain", authConnection);
}
}Use the client's credentials to fetch the access token for further use.
To learn more about Client Credentials Flow head to the Docs.
public async Task LoginWithClientCredentials()
{
var authClient = new AuthenticationApiClient("my.custom.domain");
// Fetch the access token using the Client Credentials.
var accessTokenResponse = await authClient.GetTokenAsync(new ClientCredentialsTokenRequest()
{
Audience = "audience",
ClientId = "clientId",
ClientSecret = "clientSecret",
});
Console.WriteLine($"Access Token : {accessTokenResponse.AccessToken}");
Console.WriteLine($"Refresh Token : {accessTokenResponse.RefreshToken}");
Console.WriteLine($"Id Token : {accessTokenResponse.IdToken}");
Console.WriteLine($"Token Type : {accessTokenResponse.TokenType}");
Console.WriteLine($"Expires In : {accessTokenResponse.ExpiresIn}");
}Head here to undertstand more about this flow and to get a glimpse of the pre-requisites required for this flow.
- When the user tries to login for the first time since enabling MFA, we would encounter an
ErrorApiExceptionexception indicating that MFA is required. - We then extract the
mfa_tokenfrom the exception and use that to enroll / challenge the user.
public async Task LoginWithRopgWithMfa()
{
var authClient = new AuthenticationApiClient("my.custom.domain");
string mfaToken = "";
try
{
var accessTokenResponse = await authClient.GetTokenAsync(new ResourceOwnerTokenRequest()
{
ClientId = "_clientId",
ClientSecret = "_clientSecret",
Username = "userName",
Password = "password",
Scope = "openid profile email", // Depends on the scopes you want to request.
Realm = "realm"
});
} catch (ErrorApiException ex)
{
// Indicates that MFA is required.
mfaToken = ex.ApiError.ExtraData["mfa_token"];
}
}- Using the
mfa_tokenwe trigger a flow that will send out a code to the user.
// Example of Enrolling the user
var resp = await authClient.AssociateMfaAuthenticatorAsync(
new AssociateMfaAuthenticatorRequest
{
ClientId = "_clientId",
ClientSecret = "_clientSecret",
OobChannels = new List<string> { "sms" },
AuthenticatorTypes = new[] { "oob" },
Token = mfaToken,
PhoneNumber = "+911234567890" // Users phone-number which will receive the code.
});var token = await authClient.GetTokenAsync(new MfaOobTokenRequest()
{
ClientId = _clientId,
ClientSecret = _clientSecret,
MfaToken = mfaToken,
OobCode = resp.OobCode,
BindingCode = "sms" // the Code received by the user from the previous call.
}
);var response = await authClient.MfaChallenge(
new MfaChallengeRequest()
{
ClientId = _clientId,
ClientSecret = _clientSecret,
MfaToken = mfaToken,
ChallengeType = "oob",
AuthenticatorId = "authenticatorId"
});var response = await authClient.ListMfaAuthenticatorsAsync("mfaToken");await authClient.DeleteMfaAuthenticatorAsync(
new DeleteMfaAuthenticatorRequest()
{
AccessToken = "AccessToken",
AuthenticatorId = "id-random"
});Rich Authorization Requests (RAR) let you pass fine-grained, structured authorization data via the
authorization_details parameter. Each entry is an AuthorizationDetail identified by its Type; any
additional, type-specific fields are supplied through AdditionalData.
Set AuthorizationDetailsObjects to pass structured authorization details. When set, it takes precedence
over the raw AuthorizationDetails string.
var response = await authClient.PushedAuthorizationRequestAsync(
new PushedAuthorizationRequest()
{
ClientId = _clientId,
ClientSecret = _clientSecret,
ResponseType = AuthorizationResponseType.Code,
RedirectUri = "https://www.myapp.com/callback",
AuthorizationDetailsObjects = new List<AuthorizationDetail>
{
new AuthorizationDetail
{
Type = "payment_initiation",
AdditionalData = new Dictionary<string, JsonElement>
{
["instructedAmount"] = JsonSerializer.SerializeToElement(new { currency = "EUR", amount = "123.50" })
}
}
}
});Pass AuthorizationDetailsObjects on the request, and read the strongly-typed details back from the token response.
var authorizationResponse = await authClient.ClientInitiatedBackchannelAuthorization(
new ClientInitiatedBackchannelAuthorizationRequest()
{
ClientId = _clientId,
ClientSecret = _clientSecret,
BindingMessage = "ABC-123",
Scope = "openid",
LoginHint = new LoginHint { Format = "iss_sub", Issuer = _issuer, Subject = _subject },
AuthorizationDetailsObjects = new List<AuthorizationDetail>
{
new AuthorizationDetail { Type = "payment_initiation" }
}
});
var tokenResponse = await authClient.GetTokenAsync(
new ClientInitiatedBackchannelAuthorizationTokenRequest()
{
AuthRequestId = authorizationResponse.AuthRequestId,
ClientId = _clientId,
ClientSecret = _clientSecret
});
// Strongly-typed authorization details returned by the token endpoint.
// AuthorizationDetails is null when the token endpoint returns no authorization details.
if (tokenResponse.AuthorizationDetails != null)
{
foreach (var detail in tokenResponse.AuthorizationDetails)
{
Console.WriteLine(detail.Type);
}
}A Multi-Resource Refresh Token lets you exchange a single refresh token for access
tokens targeting different APIs (Audience) and/or broader scopes, without a full
re-authentication. At the protocol level this is a standard refresh-token grant with
Audience and/or Scope set.
The token endpoint returns the scopes that were actually granted in
AccessTokenResponse.Scope (RFC 6749 §5.1), which may be narrower than what you
requested. Compare the two to detect an insufficient-scope grant.
var requestedScope = "read:data write:data";
var tokenResponse = await authClient.GetTokenAsync(new RefreshTokenRequest
{
ClientId = _clientId,
ClientSecret = _clientSecret,
RefreshToken = _refreshToken,
Audience = "https://my-api.example.com", // target a different API (optional)
Scope = requestedScope // request broader scopes (optional)
});
Console.WriteLine($"Access Token : {tokenResponse.AccessToken}");
Console.WriteLine($"Granted Scope: {tokenResponse.Scope}");
// The server may grant fewer scopes than requested, and returns them in no guaranteed
// order - compare as sets rather than comparing the raw strings.
var requested = requestedScope.Split(' ', StringSplitOptions.RemoveEmptyEntries);
var granted = (tokenResponse.Scope ?? string.Empty).Split(' ', StringSplitOptions.RemoveEmptyEntries);
var missingScopes = requested.Except(granted).ToList();
if (missingScopes.Count > 0)
{
Console.WriteLine($"Warning: not all requested scopes were granted. Missing: {string.Join(" ", missingScopes)}");
}Custom Token Exchange uses the OAuth 2.0 Token Exchange grant (RFC 8693) to exchange an existing token for a new Auth0-issued token. The same call supports two use cases, selected by the parameters you pass.
using Auth0.AuthenticationApi;
using Auth0.AuthenticationApi.Models;
var auth = new AuthenticationApiClient("YOUR_AUTH0_DOMAIN");
var tokenResponse = await auth.GetTokenAsync(new TokenExchangeTokenRequest
{
ClientId = "YOUR_CLIENT_ID",
ClientSecret = "YOUR_CLIENT_SECRET",
SubjectToken = "THE_TOKEN_TO_EXCHANGE",
SubjectTokenType = "https://acme.com/cte-profile", // your Custom Token Exchange Profile identifier
Audience = "https://api.example.com", // optional target API
Scope = "read:data" // optional
});
Console.WriteLine($"Access Token : {tokenResponse.AccessToken}");
Console.WriteLine($"Issued Token Type: {tokenResponse.IssuedTokenType}");Set Audience to your tenant's session-transfer audience and supply an actor token. The
response's IssuedTokenType will be TokenType.SessionTransferToken.
var sttResponse = await auth.GetTokenAsync(new TokenExchangeTokenRequest
{
ClientId = "YOUR_CLIENT_ID",
ClientSecret = "YOUR_CLIENT_SECRET",
SubjectToken = "THE_SUBJECT_TOKEN",
SubjectTokenType = "https://acme.com/cte-profile", // your Custom Token Exchange Profile identifier
ActorToken = "THE_ACTOR_TOKEN", // the acting party's Auth0 ID token
ActorTokenType = TokenType.IdToken,
Audience = "urn:YOUR_AUTH0_DOMAIN:session_transfer"
});
if (sttResponse.IssuedTokenType == TokenType.SessionTransferToken)
{
Console.WriteLine($"Session Transfer Token: {sttResponse.AccessToken}");
}
ActorTokenandActorTokenTypeare both-or-neither - supplying only one throwsArgumentExceptionbefore any network call.
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.
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}");
Connectionis required - omitting it throwsArgumentExceptionbefore any network call.requested_token_typeis fixed to the federated-connection URN and set for you.
Exchange an incoming user access token for a short-lived token scoped to a downstream API,
then inspect the actor for audit logging. The current actor (GetCurrentActor()) is the
only value to use for authorization decisions; the delegation chain is
for audit/logging only. The exchanged token is not validated by this SDK - the
downstream resource server must validate it.
using Auth0.AuthenticationApi;
using Auth0.AuthenticationApi.Models;
var auth = new AuthenticationApiClient(new Uri("https://YOUR_DOMAIN"));
var response = await auth.GetTokenOnBehalfOfAsync(new OnBehalfOfTokenRequest
{
ClientId = "YOUR_CLIENT_ID",
ClientSecret = "YOUR_CLIENT_SECRET",
SubjectToken = incomingUserAccessToken,
Audience = "https://calendar-api.acme.com",
Scope = "calendar:read calendar:write"
});
string downstreamToken = response.AccessToken; // short-lived, non-refreshable
// Authorization: use ONLY the current actor.
string? currentActor = response.GetCurrentActor(); // e.g. "mcp_server_client_id"
if (currentActor is null)
{
throw new UnauthorizedAccessException("Exchanged token has no actor claim.");
}
// Audit/logging only - do NOT use nested prior actors for access control.
Actor? chain = response.GetDelegationChain();- 1. Management Client Initialization
- 2. Update M2M Token Quota at different levels
- 3. Get Job Error Details
- 4. Manage Network Access Control Lists (ACLs)
- 5. Multiple Custom Domain (MCD) Header
The recommended way to initialize the Management API client is using the ManagementClient wrapper, which abstracts token management via an ITokenProvider.
Client credentials (recommended - tokens are acquired and refreshed automatically):
using Auth0.ManagementApi;
public async Task Initialize()
{
var client = new ManagementClient(new ManagementClientOptions
{
Domain = "my.custom.domain",
TokenProvider = new ClientCredentialsTokenProvider(
domain: "my.custom.domain",
clientId: "clientId",
clientSecret: "clientSecret"
)
});
// Tokens are acquired and refreshed automatically
var users = await client.Users.ListAsync(new ListUsersRequestParameters());
}Async delegate (retrieve tokens from an external source such as a vault):
var client = new ManagementClient(new ManagementClientOptions
{
Domain = "my.custom.domain",
TokenProvider = new DelegateTokenProvider(ct => GetTokenFromVaultAsync(ct))
});If you prefer to manage tokens yourself, you can use the ManagementApiClient directly:
using Auth0.ManagementApi;
public async Task InitializeWithManualToken()
{
var authClient = new AuthenticationApiClient("my.custom.domain");
// Fetch the access token using the Client Credentials.
var accessTokenResponse = await authClient.GetTokenAsync(new ClientCredentialsTokenRequest()
{
Audience = "https://my.custom.domain/api/v2/",
ClientId = "clientId",
ClientSecret = "clientSecret",
});
var apiClient = new ManagementApiClient(
token: accessTokenResponse.AccessToken,
clientOptions: new ClientOptions { BaseUrl = "https://my.custom.domain/api/v2" });
}Assuming you have a ManagementClient or ManagementApiClient initialized as shown above.
using Auth0.ManagementApi;
var tenantUpdateSettings = new UpdateTenantSettingsRequestContent
{
DefaultTokenQuota = new DefaultTokenQuota
{
Clients = new TokenQuotaConfiguration
{
ClientCredentials = new TokenQuotaClientCredentials
{
Enforce = true,
PerDay = 200,
PerHour = 100
}
},
Organizations = new TokenQuotaConfiguration
{
ClientCredentials = new TokenQuotaClientCredentials
{
Enforce = true,
PerDay = 200,
PerHour = 100
}
}
}
};
var updatedSettings = await client.Tenants.Settings.UpdateAsync(tenantUpdateSettings);Assuming you have a ManagementClient or ManagementApiClient initialized as shown above.
using Auth0.ManagementApi;
using Auth0.ManagementApi.Core;
var clientUpdateRequest = new UpdateClientRequestContent
{
TokenQuota = Optional<UpdateTokenQuota?>.Of(new UpdateTokenQuota
{
ClientCredentials = new TokenQuotaClientCredentials
{
Enforce = true,
PerDay = 200,
PerHour = 100
}
})
};
var clientUpdateResponse = await client.Clients.UpdateAsync("client_id", clientUpdateRequest);Assuming you have a ManagementClient or ManagementApiClient initialized as shown above.
using Auth0.ManagementApi;
using Auth0.ManagementApi.Core;
var orgUpdateRequest = new UpdateOrganizationRequestContent
{
TokenQuota = Optional<UpdateTokenQuota?>.Of(new UpdateTokenQuota
{
ClientCredentials = new TokenQuotaClientCredentials
{
Enforce = true,
PerDay = 200,
PerHour = 100
}
})
};
var orgUpdateResponse = await client.Organizations.UpdateAsync("org_id", orgUpdateRequest);When a job fails, you can get the error details using the Jobs.Errors.GetAsync method.
The response is an undiscriminated union type that will contain either IEnumerable<GetJobErrorResponseContent> (for import job errors) or GetJobGenericErrorResponseContent (for other job errors).
Assuming you have a ManagementClient or ManagementApiClient initialized as shown above.
using Auth0.ManagementApi;
public async Task GetJobErrorDetails(string jobId)
{
var jobError = await client.Jobs.Errors.GetAsync(jobId);
// Handle the response based on its type using Visit() for side effects
jobError.Visit(
importErrors =>
{
// Handle import job errors (IEnumerable<GetJobErrorResponseContent>)
foreach (var error in importErrors)
{
Console.WriteLine($"User object: {error.User}");
if (error.Errors != null)
{
foreach (var err in error.Errors)
{
Console.WriteLine($"Error Code: {err.Code}");
Console.WriteLine($"Error Message: {err.Message}");
Console.WriteLine($"Error Path: {err.Path}");
}
}
}
},
genericError =>
{
// Handle generic job errors (GetJobGenericErrorResponseContent)
Console.WriteLine($"Job Type: {genericError.Type}");
Console.WriteLine($"Job Status: {genericError.Status}");
Console.WriteLine($"Job Id: {genericError.Id}");
Console.WriteLine($"Job Connection Id: {genericError.ConnectionId}");
Console.WriteLine($"Job Created At: {genericError.CreatedAt}");
Console.WriteLine($"Job Status Details: {genericError.StatusDetails}");
}
);
// Alternative: Use Match() to return a value
var summary = jobError.Match(
importErrors => $"Import job with {importErrors.Count()} errors",
genericError => $"Job {genericError.Id} failed with status: {genericError.Status}"
);
Console.WriteLine(summary);
// Alternative: Use TryGet methods for conditional access
if (jobError.TryGetListOfGetJobErrorResponseContent(out var errors))
{
Console.WriteLine($"Found {errors!.Count()} import errors");
}
else if (jobError.TryGetGetJobGenericErrorResponseContent(out var generic))
{
Console.WriteLine($"Generic error: {generic!.StatusDetails}");
}
}You can read more about ACLs on the Docs
Assuming a scenario where you want to create a Network ACL that blocks traffic from a specific country,
you can use the following code snippet. This example uses the NetworkAcls client to create a new Network ACL
with a rule that blocks traffic from an Imaginary country (IMG).
using Auth0.ManagementApi;
public async Task CreateNetworkAcl()
{
var networkAcl = new CreateNetworkAclRequestContent
{
Active = true,
Priority = 1,
Description = "Reject all traffic from imaginary Country",
Rule = new NetworkAclRule
{
Action = new NetworkAclAction { Block = true },
Match = new NetworkAclMatch
{
GeoCountryCodes = new List<string> { "IMG" }
},
Scope = NetworkAclRuleScopeEnum.Management
}
};
await client.NetworkAcls.CreateAsync(networkAcl);
}using Auth0.ManagementApi;
public async Task GetAllNetworkAcls()
{
var pager = await client.NetworkAcls.ListAsync(new ListNetworkAclsRequestParameters());
await foreach (var acl in pager)
{
Console.WriteLine($"Network ACL ID: {acl.Id}");
Console.WriteLine($"Description: {acl.Description}");
Console.WriteLine($"Priority: {acl.Priority}");
Console.WriteLine($"Active: {acl.Active}");
Console.WriteLine($"Created At: {acl.CreatedAt}");
Console.WriteLine($"Updated At: {acl.UpdatedAt}");
}
}using Auth0.ManagementApi;
public async Task GetNetworkAcl(string aclId)
{
var networkAcl = await client.NetworkAcls.GetAsync(aclId);
Console.WriteLine($"Network ACL ID: {networkAcl.Id}");
Console.WriteLine($"Description: {networkAcl.Description}");
Console.WriteLine($"Priority: {networkAcl.Priority}");
Console.WriteLine($"Active: {networkAcl.Active}");
Console.WriteLine($"Created At: {networkAcl.CreatedAt}");
Console.WriteLine($"Updated At: {networkAcl.UpdatedAt}");
}Assuming you have the id of the ACL to update, you can use the following code snippet.
using Auth0.ManagementApi;
public async Task UpdateNetworkAcl(string aclId)
{
var updateRequest = new UpdateNetworkAclRequestContent
{
Active = false,
Priority = 2,
Description = "Updated description for examples",
Rule = new NetworkAclRule
{
Action = new NetworkAclAction { Block = true },
Match = new NetworkAclMatch
{
GeoCountryCodes = new List<string> { "US" }
},
NotMatch = new NetworkAclMatch
{
GeoCountryCodes = new List<string> { "CA" }
},
Scope = NetworkAclRuleScopeEnum.Management
}
};
await client.NetworkAcls.UpdateAsync(aclId, updateRequest);
}Assuming you have the id of the ACL to update, you can use the following code snippet.
using Auth0.ManagementApi;
public async Task SetNetworkAcl(string aclId)
{
var setRequest = new SetNetworkAclRequestContent
{
Active = false,
Priority = 2,
Description = "Updated description for examples",
Rule = new NetworkAclRule
{
Action = new NetworkAclAction { Block = true },
Match = new NetworkAclMatch
{
GeoCountryCodes = new List<string> { "US" }
},
NotMatch = new NetworkAclMatch
{
GeoCountryCodes = new List<string> { "CA" }
},
Scope = NetworkAclRuleScopeEnum.Management
}
};
await client.NetworkAcls.SetAsync(aclId, setRequest);
}Auth0 tenants with Multiple Custom Domains enabled must supply the Auth0-Custom-Domain header
on the Management API endpoints that generate user-facing links. The affected endpoints are:
POST /api/v2/tickets/email-verificationPOST /api/v2/tickets/password-changePOST /api/v2/organizations/{id}/invitationsPOST /api/v2/guardian/enrollments/ticketPOST /api/v2/jobs/verification-emailPOST /api/v2/jobs/users-imports(whenverify_email: true)POST /api/v2/users(Create)PATCH /api/v2/users/{id}(Update, whenverify_email: true)
When CustomDomain is set on ManagementClientOptions and no custom HttpClient is provided,
the SDK automatically configures a CustomDomainInterceptor that strips the header from any
endpoint not on the whitelist above.
using Auth0.ManagementApi;
public async Task UseCustomDomainGlobal()
{
var client = new ManagementClient(new ManagementClientOptions
{
Domain = "my.auth0.domain",
TokenProvider = new ClientCredentialsTokenProvider(
domain: "my.auth0.domain",
clientId: "clientId",
clientSecret: "clientSecret"
),
CustomDomain = "login.mycompany.com"
});
// Auth0-Custom-Domain header is sent automatically on whitelisted endpoints
// and stripped from all others.
var ticket = await client.Tickets.VerifyEmailAsync(
new VerifyEmailTicketRequestContent { UserId = "auth0|abc123" });
Console.WriteLine($"Ticket URL: {ticket.Ticket}");
}Use CustomDomainHeader.For() to supply the header for a single call without configuring it
globally. This is useful when only a subset of calls require the header, or when you need to
use a different domain for a specific request.
using Auth0.ManagementApi;
public async Task UseCustomDomainPerRequest()
{
// Client without a global custom domain
var client = new ManagementClient(new ManagementClientOptions
{
Domain = "my.auth0.domain",
TokenProvider = new ClientCredentialsTokenProvider(
domain: "my.auth0.domain",
clientId: "clientId",
clientSecret: "clientSecret"
)
});
// Supply the header only for this call
var ticket = await client.Tickets.VerifyEmailAsync(
new VerifyEmailTicketRequestContent { UserId = "auth0|abc123" },
CustomDomainHeader.For("login.mycompany.com"));
Console.WriteLine($"Ticket URL: {ticket.Ticket}");
// Works with any whitelisted endpoint
var invitation = await client.Organizations.Invitations.CreateAsync(
"org_123",
new CreateOrganizationInvitationRequestContent
{
Inviter = new OrganizationInvitationInviter { Name = "Admin" },
Invitee = new OrganizationInvitationInvitee { Email = "user@example.com" },
ClientId = "clientId"
},
CustomDomainHeader.For("login.mycompany.com"));
}Combining with other per-request options:
CustomDomainHeader.For()is a convenience helper that returns aRequestOptionspre-populated with only theAuth0-Custom-Domainheader. When you also need to set other options on the same call (additional headers, timeout, retries), constructRequestOptionsdirectly instead:await client.Tickets.VerifyEmailAsync( new VerifyEmailTicketRequestContent { UserId = "auth0|abc123" }, new RequestOptions { AdditionalHeaders = new[] { new KeyValuePair<string, string?>("Auth0-Custom-Domain", "login.mycompany.com"), new KeyValuePair<string, string?>("X-Correlation-Id", "abc-123"), }, MaxRetries = 1, });
If you manage tokens yourself using ManagementApiClient directly, pass a CustomDomainInterceptor
as the HttpClient handler to enable automatic header stripping.
using Auth0.ManagementApi;
using Auth0.ManagementApi.Core;
public async Task UseCustomDomainWithManagementApiClient()
{
// CustomDomainInterceptor strips the header from non-whitelisted endpoints.
var client = new ManagementApiClient(
token: "your-access-token",
clientOptions: new ClientOptions
{
BaseUrl = "https://my.auth0.domain/api/v2",
CustomDomain = "login.mycompany.com",
HttpClient = new HttpClient(new CustomDomainInterceptor())
});
var ticket = await client.Tickets.VerifyEmailAsync(
new VerifyEmailTicketRequestContent { UserId = "auth0|abc123" });
Console.WriteLine($"Ticket URL: {ticket.Ticket}");
}Note: If you supply your own
HttpClientalongsideCustomDomain, theCustomDomainInterceptoris not injected automatically - you must add it yourself as shown above. Without it the header is still sent, but it will be present on every request rather than only whitelisted ones.