- Login and Logout
- Scopes
- Calling an API
- Organizations
- Extra parameters
- Roles
- Backchannel Logout
- Blazor Server
Triggering login or logout is done using ASP.NET's HttpContext
:
public async Task Login(string returnUrl = "/")
{
var authenticationProperties = new LoginAuthenticationPropertiesBuilder()
.WithRedirectUri(returnUrl)
.Build();
await HttpContext.ChallengeAsync(Auth0Constants.AuthenticationScheme, authenticationProperties);
}
[Authorize]
public async Task Logout()
{
var authenticationProperties = new LogoutAuthenticationPropertiesBuilder()
// Indicate here where Auth0 should redirect the user after a logout.
// Note that the resulting absolute Uri must be added in the
// **Allowed Logout URLs** settings for the client.
.WithRedirectUri(Url.Action("Index", "Home"))
.Build();
await HttpContext.SignOutAsync(Auth0Constants.AuthenticationScheme, authenticationProperties);
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
By default, this SDK requests the openid profile
scopes, if needed you can configure the SDK to request a different set of scopes.
As openid
is a required scope, the SDK will ensure the openid
scope is always added, even when explicitly omitted when setting the scope.
services.AddAuth0WebAppAuthentication(options =>
{
options.Domain = Configuration["Auth0:Domain"];
options.ClientId = Configuration["Auth0:ClientId"];
options.Scope = "openid profile scope1 scope2";
});
Apart from being able to configure the used scopes globally, the SDK's LoginAuthenticationPropertiesBuilder
can be used to supply scopes when triggering login through HttpContext.ChallengeAsync
:
var authenticationProperties = new LoginAuthenticationPropertiesBuilder()
.WithScope("openid profile scope1 scope2")
.Build();
await HttpContext.ChallengeAsync(Auth0Constants.AuthenticationScheme, authenticationProperties);
ℹ️ Specifying the scopes when calling
HttpContext.ChallengeAsync
will take precedence over any globally configured scopes.
If you want to call an API from your ASP.NET MVC application, you need to obtain an access token issued for the API you want to call.
As the SDK is configured to use OAuth's Implicit Grant with Form Post, no access token will be returned by default. In order to do so, we should be using the Authorization Code Grant, which requires the use of a ClientSecret
.
Next, to obtain the token to access an external API, call WithAccessToken
and set the audience
to the API Identifier. You can get the API Identifier from the API Settings for the API you want to use.
services
.AddAuth0WebAppAuthentication(options =>
{
options.Domain = Configuration["Auth0:Domain"];
options.ClientId = Configuration["Auth0:ClientId"];
options.ClientSecret = Configuration["Auth0:ClientSecret"];
})
.WithAccessToken(options =>
{
options.Audience = Configuration["Auth0:Audience"];
});
Apart from being able to configure the audience globally, the SDK's LoginAuthenticationPropertiesBuilder
can be used to supply the audience when triggering login through HttpContext.ChallengeAsync
:
var authenticationProperties = new LoginAuthenticationPropertiesBuilder()
.WithRedirectUri("/") // "/" is the default value used for RedirectUri, so this can be omitted.
.WithAudience("YOUR_AUDIENCE")
.Build();
await HttpContext.ChallengeAsync(Auth0Constants.AuthenticationScheme, authenticationProperties);
ℹ️ Specifying the Audience when calling
HttpContext.ChallengeAsync
will take precedence over any globally configured Audience.
As the SDK uses the OpenId Connect middleware, the ID token is decoded and the corresponding claims are added to the ClaimsIdentity
, making them available by using User.Claims
.
The access token can be retrieved by calling HttpContext.GetTokenAsync("access_token")
.
[Authorize]
public async Task<IActionResult> Profile()
{
var accessToken = await HttpContext.GetTokenAsync("access_token");
return View(new UserProfileViewModel()
{
Name = User.Identity.Name,
EmailAddress = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value,
ProfileImage = User.Claims.FirstOrDefault(c => c.Type == "picture")?.Value
});
}
In the case where the application needs to use an access token to access an API, there may be a situation where the access token expires before the application's session does. In order to ensure you have a valid access token at all times, you can configure the SDK to use refresh tokens:
public void ConfigureServices(IServiceCollection services)
{
services
.AddAuth0WebAppAuthentication(options =>
{
options.Domain = Configuration["Auth0:Domain"];
options.ClientId = Configuration["Auth0:ClientId"];
options.ClientSecret = Configuration["Auth0:ClientSecret"];
})
.WithAccessToken(options =>
{
options.Audience = Configuration["Auth0:Audience"];
options.UseRefreshTokens = true;
});
}
In the event where the API, defined in your Auth0 dashboard, isn't configured to allow offline access, or the user was already logged in before the use of refresh tokens was enabled (e.g. a user logs in a few minutes before the use of refresh tokens is deployed), it might be useful to detect the absense of a refresh token in order to react accordingly (e.g. log the user out locally and force them to re-login).
services
.AddAuth0WebAppAuthentication(options => {})
.WithAccessToken(options =>
{
options.Audience = Configuration["Auth0:Audience"];
options.UseRefreshTokens = true;
options.Events = new Auth0WebAppWithAccessTokenEvents
{
OnMissingRefreshToken = async (context) =>
{
await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
var authenticationProperties = new LogoutAuthenticationPropertiesBuilder().WithRedirectUri("/").Build();
await context.ChallengeAsync(Auth0Constants.AuthenticationScheme, authenticationProperties);
}
};
});
The above snippet checks whether the SDK is configured to use refresh tokens, if there is an existing ID token (meaning the user is authenticated) as well as the absence of a refresh token. If each of these criteria are met, it logs the user out from the application and initializes a new login flow.
ℹ️ In order for Auth0 to redirect back to the application's login URL, ensure to add the configured redirect URL to the application's
Allowed Logout URLs
in Auth0's dashboard.
Organizations is a set of features that provide better support for developers who build and maintain SaaS and Business-to-Business (B2B) applications.
Note that Organizations is currently only available to customers on our Enterprise and Startup subscription plans.
Log in to an organization by specifying the Organization
when calling AddAuth0WebAppAuthentication
:
services.AddAuth0WebAppAuthentication(options =>
{
options.Domain = Configuration["Auth0:Domain"];
options.ClientId = Configuration["Auth0:ClientId"];
options.Organization = Configuration["Auth0:Organization"];
});
Apart from being able to configure the organization globally, the SDK's LoginAuthenticationPropertiesBuilder
can be used to supply the organization when triggering login through HttpContext.ChallengeAsync
:
var authenticationProperties = new LoginAuthenticationPropertiesBuilder()
.WithOrganization("YOUR_ORGANIZATION_ID_OR_NAME")
.Build();
await HttpContext.ChallengeAsync(Auth0Constants.AuthenticationScheme, authenticationProperties);
ℹ️ Specifying the Organization when calling
HttpContext.ChallengeAsync
will take precedence over any globally configured Organization.
If you don't provide an organization
parameter at login, the SDK can't validate the org_id
(or org_name
) claim you get back in the ID token. In that case, you should validate the org_id
(or org_name
) claim yourself (e.g. by checking it against a list of valid organization ID's (or names) or comparing it with the application's URL).
services.AddAuth0WebAppAuthentication(options =>
{
options.Domain = Configuration["Auth0:Domain"];
options.ClientId = Configuration["Auth0:ClientId"];
options.OpenIdConnectEvents = new OpenIdConnectEvents
{
OnTokenValidated = (context) =>
{
var organizationClaimValue = context.SecurityToken.Claims.SingleOrDefault(claim => claim.Type == "org_id")?.Value;
var expectedOrganizationIds = new List<string> {"123", "456"};
if (!string.IsNullOrEmpty(organizationClaimValue) && !expectedOrganizationIds.Contains(organizationClaimValue))
{
context.Fail("Unexpected org_id claim detected.");
}
return Task.CompletedTask;
}
};
}).
For more information, please read Work with Tokens and Organizations on Auth0 Docs.
Accept a user invitation through the SDK by creating a route within your application that can handle the user invitation URL, and log the user in by passing the organization
and invitation
parameters from this URL.
public class InvitationController : Controller {
public async Task Accept(string organization, string invitation)
{
var authenticationProperties = new LoginAuthenticationPropertiesBuilder()
.WithOrganization(organization)
.WithInvitation(invitation)
.Build();
await HttpContext.ChallengeAsync(Auth0Constants.AuthenticationScheme, authenticationProperties);
}
}
Auth0's /authorize
and /v2/logout
endpoint support additional querystring parameters that aren't first-class citizens in this SDK. If you need to support any of those parameters, you can configure the SDK to do so.
In order to send extra parameters to Auth0's /authorize
endpoint upon logging in, set LoginParameters
when calling AddAuth0WebAppAuthentication
.
An example is the screen_hint
parameter, which can be used to show the signup page instead of the login page when redirecting users to Auth0:
services.AddAuth0WebAppAuthentication(options =>
{
options.Domain = Configuration["Auth0:Domain"];
options.ClientId = Configuration["Auth0:ClientId"];
options.LoginParameters = new Dictionary<string, string>() { { "screen_hint", "signup" } };
});
Apart from being able to configure these globally, the SDK's LoginAuthenticationPropertiesBuilder
can be used to supply extra parameters when triggering login through HttpContext.ChallengeAsync
:
var authenticationProperties = new LoginAuthenticationPropertiesBuilder()
.WithParameter("screen_hint", "signup")
.Build();
await HttpContext.ChallengeAsync(Auth0Constants.AuthenticationScheme, authenticationProperties);
ℹ️ Specifying any extra parameter when calling
HttpContext.ChallengeAsync
will take precedence over any globally configured parameter.
The same as with the login request, you can send parameters to the logout
endpoint by calling WithParameter
on the LogoutAuthenticationPropertiesBuilder
.
var authenticationProperties = new LogoutAuthenticationPropertiesBuilder()
.WithParameter("federated")
.Build();
await HttpContext.SignOutAsync(Auth0Constants.AuthenticationScheme, authenticationProperties);
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
ℹ️ The example above uses a parameter without an actual value, for more information see https://auth0.com/docs/logout/log-users-out-of-idps.
Before you can add Role Based Access Control, you will need to ensure the required roles are created and assigned to the corresponding user(s). Follow the guidance explained in assign-roles-to-users to ensure your user gets assigned the admin role.
Once the role is created and assigned to the required user(s), you will need to create an action that adds the role(s) to the ID token so that it is available to your backend. To do so, go to the Auth0 dashboard and create a custom action. Then, use the following code for your action:
exports.onExecutePostLogin = async (event, api) => {
const assignedRoles = (event.authorization || {}).roles;
api.idToken.setCustomClaim('http://schemas.microsoft.com/ws/2008/06/identity/claims/role', assignedRoles);
}
ℹ️ As this SDK uses the OpenId Connect middleware, it expects roles to exist in the
http://schemas.microsoft.com/ws/2008/06/identity/claims/role
claim.
You can use the Role based authorization mechanism to make sure that only the users with specific roles can access certain actions. Add the [Authorize(Roles = "...")]
attribute to your controller action.
[Authorize(Roles = "admin")]
public IActionResult Admin()
{
return View();
}
Backchannel logout can be configured by calling WithBackchannelLogout()
when calling AddAuth0WebAppAuthentication
.
services
.AddAuth0WebAppAuthentication(PlaygroundConstants.AuthenticationScheme, options =>
{
options.Domain = Configuration["Auth0:Domain"];
options.ClientId = Configuration["Auth0:ClientId"];
options.ClientSecret = Configuration["Auth0:ClientSecret"];
}).WithBackchannelLogout();
Additionally, you will also need to call UseBackchannelLogout();
on the ApplicationBuilder:
app.UseBackchannelLogout();
As logout tokens need to be stored, you will also need to provide something for our SDK to store the tokens in.
services.AddTransient<ILogoutTokenHandler, CustomLogoutTokenHandler>();
The implementation of CustomLogoutTokenHandler
will heaviliy depend on your situation, but here's a blueprint you can use:
public class CustomLogoutTokenHandler : ILogoutTokenHandler
{
public CustomLogoutTokenHandler()
{
}
public Task OnTokenReceivedAsync(string issuer, string sid, string logoutToken, TimeSpan expiration)
{
// When a token is received, you need to store it for the duration of `expiration`, using `issuer` and `sid` as the identifiers.
}
public Task<bool> IsLoggedOutAsync(string issuer, string sid)
{
// Return a boolean based on whether or not you find a logout token using the `issuer` and `sid`.
}
}
If you want to connect the backchannel logout to a distributed cache, such as redis, to store the logout tokens, you can use:
public class CustomDistributedLogoutTokenHandler : ILogoutTokenHandler
{
private readonly IDistributedCache _cache;
public CustomDistributedLogoutTokenHandler(IDistributedCache cache)
{
_cache = cache;
}
public async Task OnTokenReceivedAsync(string issuer, string sid, string logoutToken, TimeSpan expiration)
{
await _cache.SetAsync($"{issuer}|{sid}", Encoding.ASCII.GetBytes(logoutToken), new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = expiration
});
}
public async Task<bool> IsLoggedOutAsync(string issuer, string sid)
{
var token = await _cache.GetAsync($"{issuer}|{sid}");
return token != null;
}
}
The Auth0-AspNetCore-Authentication
SDK works with Blazor Server in an almost identical way as how it's integrated in ASP.NET Core MVC.
Registering the SDK is identical as with ASP.NET Core MVC, where you should call builder.Services.AddAuth0WebAppAuthentication
inside Program.cs
, and ensure the authentication middleware (UseAuthentication()
and UseAuthorization()
) is registered.
builder.Services.AddAuth0WebAppAuthentication(options =>
{
options.Domain = builder.Configuration["Auth0:Domain"];
options.ClientId = builder.Configuration["Auth0:ClientId"];
options.Scope = "openid profile email";
});
// ...
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
Adding login and logout capabilities is different in the sense that you should create a PageModel
implementation for both to allow the user to be redirected to Auth0.
public class LoginModel : PageModel
{
public async Task OnGet(string redirectUri)
{
var authenticationProperties = new LoginAuthenticationPropertiesBuilder()
.WithRedirectUri(redirectUri)
.Build();
await HttpContext.ChallengeAsync(Auth0Constants.AuthenticationScheme, authenticationProperties);
}
}
[Authorize]
public class LogoutModel : PageModel
{
public async Task OnGet()
{
var authenticationProperties = new LogoutAuthenticationPropertiesBuilder()
.WithRedirectUri("/")
.Build();
await HttpContext.SignOutAsync(Auth0Constants.AuthenticationScheme, authenticationProperties);
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
}