From 79e5c316cb7d0987e073591ef35cd1411d159cf9 Mon Sep 17 00:00:00 2001 From: Patrick Dillon Date: Wed, 13 Sep 2023 09:59:26 -0400 Subject: [PATCH] Revert "Drop quickstart for ASP.NET Core 2.1 (#10297)" This reverts commit 124aecdf1d1d5870ec5e912faa82bc190288607d. --- .../aspnet-core-webapi-2/01-authorization.md | 209 ++++++++++ .../backend/aspnet-core-webapi-2/02-using.md | 14 + .../03-troubleshooting.md | 201 +++++++++ .../backend/aspnet-core-webapi-2/download.md | 15 + .../backend/aspnet-core-webapi-2/index.yml | 49 +++ .../webapp/aspnet-core-2/00-intro.md | 23 ++ .../webapp/aspnet-core-2/01-login.md | 21 + .../webapp/aspnet-core-2/02-user-profile.md | 148 +++++++ .../webapp/aspnet-core-2/03-authorization.md | 66 +++ .../webapp/aspnet-core-2/_includes/_login.md | 380 ++++++++++++++++++ .../webapp/aspnet-core-2/_includes/_setup.md | 17 + .../webapp/aspnet-core-2/download.md | 30 ++ .../quickstart/webapp/aspnet-core-2/index.yml | 46 +++ config/redirects.js | 283 +++++++++---- 14 files changed, 1414 insertions(+), 88 deletions(-) create mode 100644 articles/quickstart/backend/aspnet-core-webapi-2/01-authorization.md create mode 100644 articles/quickstart/backend/aspnet-core-webapi-2/02-using.md create mode 100644 articles/quickstart/backend/aspnet-core-webapi-2/03-troubleshooting.md create mode 100644 articles/quickstart/backend/aspnet-core-webapi-2/download.md create mode 100644 articles/quickstart/backend/aspnet-core-webapi-2/index.yml create mode 100644 articles/quickstart/webapp/aspnet-core-2/00-intro.md create mode 100644 articles/quickstart/webapp/aspnet-core-2/01-login.md create mode 100644 articles/quickstart/webapp/aspnet-core-2/02-user-profile.md create mode 100644 articles/quickstart/webapp/aspnet-core-2/03-authorization.md create mode 100644 articles/quickstart/webapp/aspnet-core-2/_includes/_login.md create mode 100644 articles/quickstart/webapp/aspnet-core-2/_includes/_setup.md create mode 100644 articles/quickstart/webapp/aspnet-core-2/download.md create mode 100644 articles/quickstart/webapp/aspnet-core-2/index.yml diff --git a/articles/quickstart/backend/aspnet-core-webapi-2/01-authorization.md b/articles/quickstart/backend/aspnet-core-webapi-2/01-authorization.md new file mode 100644 index 0000000000..1e2d4a33c4 --- /dev/null +++ b/articles/quickstart/backend/aspnet-core-webapi-2/01-authorization.md @@ -0,0 +1,209 @@ +--- +title: Authorization +description: This tutorial demonstrates how to add authorization to an ASP.NET Core Web API 2.1 application using the standard JWT middleware. +budicon: 500 +topics: + - quickstart + - backend + - aspnetcore + - web-api +github: + path: Quickstart/01-Authorization +contentType: tutorial +useCase: quickstart +--- + + +<%= include('../../../_includes/_api_auth_intro') %> + +<%= include('../_includes/_api_create_new') %> + +<%= include('../_includes/_api_auth_preamble') %> + +## Configure the Sample project + +The sample code has an `appsettings.json` file which configures it to use the correct Auth0 **Domain** and **API Identifier** for your API. If you download the code from this page while logged in, it will be automatically filled. If you use the example from Github, you will need to fill it yourself. + +```json +{ + "Auth0": { + "Domain": "${account.namespace}", + "ApiIdentifier": "${apiIdentifier}" + } +} +``` + +## Validate Access Tokens + +### Install dependencies + +The seed project references the new ASP.NET Core metapackage (`Microsoft.AspNetCore.All`), which includes all the NuGet packages that are a part of the ASP.NET Core 2.1 framework. + +If you are not using the `Microsoft.AspNetCore.All` metapackage, add the `Microsoft.AspNetCore.Authentication.JwtBearer` package to your application. + +```text +Install-Package Microsoft.AspNetCore.Authentication.JwtBearer +``` + +### Configure the middleware + +The ASP.NET Core JWT Bearer authentication handler downloads the JSON Web Key Set (JWKS) file with the public key. The handler uses the JWKS file and the public key to verify the Access Token's signature. + +In your application, register the authentication services: + +1. Make a call to the `AddAuthentication` method. Configure the JWT Bearer tokens as the default authentication and challenge schemes. +2. Make a call to the `AddJwtBearer` method to register the JWT Bearer authentication scheme. Configure your Auth0 domain as the authority, and your Auth0 API identifier as the audience. In some cases the access token will not have a `sub` claim which will lead to `User.Identity.Name` being `null`. If you want to map a different claim to `User.Identity.Name` then add it to `options.TokenValidationParameters` within the `AddAuthentication()` call. + +```csharp +// Startup.cs + +public void ConfigureServices(IServiceCollection services) +{ + // Some code omitted for brevity... + + string domain = $"https://{Configuration["Auth0:Domain"]}/"; + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }).AddJwtBearer(options => + { + options.Authority = domain; + options.Audience = Configuration["Auth0:ApiIdentifier"]; + options.TokenValidationParameters = new TokenValidationParameters + { + NameClaimType = ClaimTypes.NameIdentifier + }; + }); +} +``` + +To add the authentication middleware to the middleware pipeline, add a call to the `UseAuthentication` method: + +```csharp +// Startup.cs + +public void Configure(IApplicationBuilder app, IHostingEnvironment env) +{ + // Some code omitted for brevity... + + app.UseAuthentication(); + + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); +} +``` + +### Validate scopes + +To make sure that an Access Token contains the correct scope, use the [Policy-Based Authorization](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies) in ASP.NET Core. + +Create a new authorization requirement called `HasScopeRequirement`. This requirement checks if the `scope` claim issued by your Auth0 tenant is present. If the `scope` claim exists, the requirement checks if the `scope` claim contains the requested scope. + +```csharp +// HasScopeRequirement.cs + +public class HasScopeRequirement : IAuthorizationRequirement +{ + public string Issuer { get; } + public string Scope { get; } + + public HasScopeRequirement(string scope, string issuer) + { + Scope = scope ?? throw new ArgumentNullException(nameof(scope)); + Issuer = issuer ?? throw new ArgumentNullException(nameof(issuer)); + } +} +``` + +```csharp +// HasScopeHandler.cs + +public class HasScopeHandler : AuthorizationHandler +{ + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, HasScopeRequirement requirement) + { + // If user does not have the scope claim, get out of here + if (!context.User.HasClaim(c => c.Type == "scope" && c.Issuer == requirement.Issuer)) + return Task.CompletedTask; + + // Split the scopes string into an array + var scopes = context.User.FindFirst(c => c.Type == "scope" && c.Issuer == requirement.Issuer).Value.Split(' '); + + // Succeed if the scope array contains the required scope + if (scopes.Any(s => s == requirement.Scope)) + context.Succeed(requirement); + + return Task.CompletedTask; + } +} +``` + +In your `ConfigureServices` method, add a call to the `AddAuthorization` method. To add policies for the scopes, call `AddPolicy` for each scope. Also ensure that you register the `HasScopeHandler` as a singleton: + +```csharp +// Startup.cs + +public void ConfigureServices(IServiceCollection services) +{ + //... + + services.AddAuthorization(options => + { + options.AddPolicy("read:messages", policy => policy.Requirements.Add(new HasScopeRequirement("read:messages", domain))); + }); + + // register the scope authorization handler + services.AddSingleton(); +} +``` + +## Protect API Endpoints + +The JWT middleware integrates with the standard ASP.NET Core [Authentication](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/) and [Authorization](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/) mechanisms. + +To secure an endpoint, you need to add the `[Authorize]` attribute to your controller action: + +```csharp +// Controllers/ApiController.cs + +[Route("api")] +public class ApiController : Controller +{ + [HttpGet] + [Route("private")] + [Authorize] + public IActionResult Private() + { + return Json(new + { + Message = "Hello from a private endpoint! You need to be authenticated to see this." + }); + } +} +``` + +To secure endpoints that require specific scopes, we need to make sure that the correct scope is present in the `access_token`. To do that, add the `Authorize` attribute to the `Scoped` action, passing `read:messages` as the `policy` parameter. + +```csharp +// Controllers/ApiController.cs + +[Route("api")] +public class ApiController : Controller +{ + [HttpGet] + [Route("private-scoped")] + [Authorize("read:messages")] + public IActionResult Scoped() + { + return Json(new + { + Message = "Hello from a private endpoint! You need to be authenticated and have a scope of read:messages to see this." + }); + } +} +``` diff --git a/articles/quickstart/backend/aspnet-core-webapi-2/02-using.md b/articles/quickstart/backend/aspnet-core-webapi-2/02-using.md new file mode 100644 index 0000000000..143001841d --- /dev/null +++ b/articles/quickstart/backend/aspnet-core-webapi-2/02-using.md @@ -0,0 +1,14 @@ +--- +title: Using your API +description: This tutorial will show you how to use your API. +budicon: 500 +topics: + - quickstart + - backend + - aspnetcore + - web-api +contentType: tutorial +useCase: quickstart +--- + +<%= include('../_includes/_api_using') %> \ No newline at end of file diff --git a/articles/quickstart/backend/aspnet-core-webapi-2/03-troubleshooting.md b/articles/quickstart/backend/aspnet-core-webapi-2/03-troubleshooting.md new file mode 100644 index 0000000000..d70326c931 --- /dev/null +++ b/articles/quickstart/backend/aspnet-core-webapi-2/03-troubleshooting.md @@ -0,0 +1,201 @@ +--- +title: Troubleshooting +description: This document will help you troubleshoot your configuration if you get 401 (Unauthorized) response from your API. +budicon: 500 +topics: + - quickstart + - backend + - aspnetcore + - web-api +contentType: tutorial +useCase: quickstart +--- + + +If the configuration of your JSON Web Token (JWT) middleware does not match the JWT that was passed to the API, you get a 401 (Unauthorized) response from your API. + +This document will help you troubleshoot your JWT middleware configuration. + +## Check the Token Validation + +There are 5 criteria for validating a JWT token. + +1. **Is the token formed properly?** +Check if the structure of the token matches the structure of a JSON Web Token. Read more about the [JSON Web Token structure](/jwt#what-is-the-json-web-token-structure-). + +2. **Has the token been tampered with?** +The last part of a JWT is the signature. The signature is used to verify that the token was signed by the sender and not altered in any way. + +3. **Has the token been received in its validity period?** +JWTs are only valid for a specified time, defined in the `exp` claim. + +4. **Is the token coming from the intended Authority?** +Check the following two criteria: + + * **Signature verification**: Check if the JWT is correctly signed with the key issued by the issuing authority. + + * **Issuer value**: The Issuer is defined in the `iss` claim. Check if this claim matches up with what your application expects. + +5. **Is the token intended for the current application?** +Check if the `aud` claim of the JWT matches with what your application expects. + +## Inspect a Token + +You can inspect a JWT with the [JWT.io](https://jwt.io/) website. Use the debugger on the website to check if your JWT is well formed. You can also inspect values of the various claims. + +The screenshot below shows the following information: +* The token is signed with the RS256 algorithm +* The issuer of the token is `https://example.auth0.com/` +* The audience of the token is `https://rs256.test.api` + +![Debugging a JWT on JWT.io](/media/articles/server-apis/aspnet-core-webapi/jwt-io-debugger-rs256.png) + +Check if the values of the JWT token match exactly the values in your JWT middleware registration. This includes the trailing slash for the Issuer. + +```csharp +var options = new JwtBearerOptions +{ + Audience = "https://rs256.test.api", + Authority = "https://example.auth0.com/" +}; + +app.UseJwtBearerAuthentication(options); +``` + +If your token is signed with the HS256 algorithm, the debugger view is different. + +The screenshot below shows the following information: +* The token is signed with the HS256 algorithm +* The issuer of the token is `https://example.auth0.com/` +* The audience of the token is `https://hs256.test.api` + +![Debugging a JWT on JWT.io](/media/articles/server-apis/aspnet-core-webapi/jwt-io-debugger-hs256.png) + +If your token is signed with the HS256 algorithm, the middleware needs to be configured in the following way: + +```csharp +var options = new JwtBearerOptions +{ + TokenValidationParameters = + { + ValidIssuer = "https://example.auth0.com/", + ValidAudience = "https://hs256.test.api", + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your api secret")) + } +}; +app.UseJwtBearerAuthentication(options); +``` + +## Debug Configuration Issues Using Log Files + +To debug potential configuration issues, inspect the log files for your application. For more information, refer to the [Logging in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging) document. + +In this example, we run the application from the command line and inspect the console log output. + +### 1. Are you passing the JWT in the Authorization header? + +Check if you are passing the JWT as a Bearer token in the `Authorization` header of the HTTP request. + +If you are not passing the token, you will see the following warning: + +![Not specifying an Authorization Header](/media/articles/server-apis/aspnet-core-webapi/troubleshoot-no-authorization-header.png) + +Look for the following warning message: + +```text +Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter' +``` + +To resolve this issue, make sure you are passing the JWT as the Bearer token in the `Authorization` header of the HTTP request. + +### 2. Did you configure the JWT middleware for the correct signing algorithm? + +Make sure that the [signing algorithm](/tokens/concepts/signing-algorithms) you used to sign your token matches the signing algorithm configured in your middleware. + +The following screenshots show two messages: +* A warning message: "Authorization failed..." +* A message with more information + +The following example shows that the JWT is signed with the HS256 algorithm and the middleware is configured to expect RS256 tokens: + +![Wrong Signature Configured](/media/articles/server-apis/aspnet-core-webapi/troubleshoot-wrong-signature-rs256.png) + +Look for the following warning message: + +```text +System.ArgumentException: IDX10634: Unable to create the SignatureProvider. + +SignatureAlgorithm: 'HS256', SecurityKey: 'Microsoft.IdentityModel.Tokens.RsaSecurityKey' is not supported. +``` + +The following example shows that the JWT is signed with the RS256 algorithm and the middleware is configured to expect HS256 tokens: + +![Wrong Signature Configured](/media/articles/server-apis/aspnet-core-webapi/troubleshoot-wrong-signature-hs256.png) + +Look for the following warning message: + +```text +Bearer was not authenticated. Failure message: IDX10501: Signature validation failed. Unable to match 'kid': 'NTF...' +``` + +To resolve this issue, make sure that the algorithm for the JWT matches with the configuration of your middleware. + +### 3. Has your token expired? + +Each JSON Web Token is valid until the time defined in the `exp` claim runs out. If you send an expired token, the token will be rejected: + +![Token Expired](/media/articles/server-apis/aspnet-core-webapi/troubleshoot-token-expired.png) + +Look for the following error message: + +```text +IDX10223: Lifetime validation failed. The token is expired +``` + +To resolve this issue, check if the token you are sending has not expired. + +::: note +The value of the `exp` claim is a numeric value representing the number of seconds from 1970-01-01T00:00:00Z UTC until the specified UTC date/time. If you want to see the date/time for the value, visit [EpochConverter](http://www.epochconverter.com/). +::: + +### 4. Did you configure the correct issuer? + +The Issuer specified in your token must match exactly with your JWT middleware configuration. + +![Issuer Validation Failed](/media/articles/server-apis/aspnet-core-webapi/troubleshoot-issuer-validation-failed.png) + +Look for the following warning message: + +::: note +You will get this message only for the tokens signed with the HS256 algorithm. +::: + +```text +IDX10205: Issuer validation failed. +``` + +To resolve this issue, make sure that you specify the correct issuer for your JWT middleware. + +For HS256 signed tokens, specify the correct value for the `ValidIssuer` property of `TokenValidationParameters`. + +::: note +For RS256 tokens, the JWT middleware downloads the OIDC discovery document from `Authority` and configures the Issuer based on the `issuer` attribute specified in that document. + +If you are using RS256 tokens, the system checks their signature before it checks the Issuer. +::: + +### 5. Does the audience match your JWT middleware configuration? + +Check if the audience specified in your token matches your JWT middleware configuration. + +![Audience Validation Failed](/media/articles/server-apis/aspnet-core-webapi/troubleshoot-audience-validation-failed.png) + +Look for the following error message: + +```text +IDX10214: Audience validation failed +``` + +To resolve this issue, make sure you specify the correct audience for your JWT middleware. Depending on how your JWT middleware was configured, do the following: +* Set the correct `Audience` property of `JwtBearerOptions` +* Set the `ValidAudience` property of `TokenValidationParameters` \ No newline at end of file diff --git a/articles/quickstart/backend/aspnet-core-webapi-2/download.md b/articles/quickstart/backend/aspnet-core-webapi-2/download.md new file mode 100644 index 0000000000..667eb06da6 --- /dev/null +++ b/articles/quickstart/backend/aspnet-core-webapi-2/download.md @@ -0,0 +1,15 @@ +To run the sample you need [.NET Core](https://www.microsoft.com/net/download) installed, and run the following commands: + +```bash +dotnet restore +dotnet run +``` + +The sample includes a [Docker](https://www.docker.com) image ready to run with the following command: + +```bash +# In Linux / macOS +sh exec.sh +# In Windows' Powershell +./exec.ps1 +``` \ No newline at end of file diff --git a/articles/quickstart/backend/aspnet-core-webapi-2/index.yml b/articles/quickstart/backend/aspnet-core-webapi-2/index.yml new file mode 100644 index 0000000000..bb67132241 --- /dev/null +++ b/articles/quickstart/backend/aspnet-core-webapi-2/index.yml @@ -0,0 +1,49 @@ +title: ASP.NET Core Web API v2.1 +# TODO remove 'image' once new QS page is live. Then only use 'logo'. +image: /media/platforms/asp.png +logo: dotnet +alias: + - asp.net core webapi + - aspnet core webapi + - asp.net core webapi 2.1 +language: + - C# +framework: + - ASP.NET Core +author: + name: Damien Guard + email: damien.guard@auth0.com + community: false +thirdParty: false +topics: + - quickstart +contentType: tutorial +useCase: quickstart +snippets: + dependencies: server-apis/aspnet-core-webapi/dependencies +articles: + - 01-authorization + - 02-using + - 03-troubleshooting +show_steps: true +github: + org: auth0-samples + repo: auth0-aspnetcore-webapi-samples + branch: netcore2.1 +requirements: + - .NET Core 2.1 +next_steps: + - path: 01-authorization + list: + - text: Configure other identity providers + icon: 345 + href: "/identityproviders" + - text: Enable multifactor authentication + icon: 345 + href: "/multifactor-authentication" + - text: Learn about attack protection + icon: 345 + href: "/attack-protection" + - text: Learn about rules + icon: 345 + href: "/rules" \ No newline at end of file diff --git a/articles/quickstart/webapp/aspnet-core-2/00-intro.md b/articles/quickstart/webapp/aspnet-core-2/00-intro.md new file mode 100644 index 0000000000..05d436c444 --- /dev/null +++ b/articles/quickstart/webapp/aspnet-core-2/00-intro.md @@ -0,0 +1,23 @@ +--- +title: Introduction +name: Introduction to the quickstart guide and configuring the environment +description: Introduction to the quickstart guide and configuring the environment. +budicon: 715 +topics: + - quickstarts + - webapp + - aspnet-core +contentType: tutorial +useCase: quickstart +--- + + +## Seed Project + +If you want to follow along with this quickstart guide, you can download the [seed project](https://github.com/auth0-samples/auth0-aspnetcore-mvc-samples/tree/master/Quickstart/00-Starter-Seed). The sample contains an ASP.NET MVC application with a home page and some NuGet packages. It also contains an `appSettings.json` file, where you can configure the Auth0-related settings for your application. + +To see what the project looks like after each step, check the [Quickstart folder](https://github.com/auth0-samples/auth0-aspnetcore-mvc-samples/tree/master/Quickstart) in the ASP.NET Core MVC Samples repository. + +<%= include('../../../_includes/_new_app') %> + +<%= include('./_includes/_setup') %> diff --git a/articles/quickstart/webapp/aspnet-core-2/01-login.md b/articles/quickstart/webapp/aspnet-core-2/01-login.md new file mode 100644 index 0000000000..8c99b4cfc0 --- /dev/null +++ b/articles/quickstart/webapp/aspnet-core-2/01-login.md @@ -0,0 +1,21 @@ +--- +title: Login +description: This tutorial demonstrates how to add user login to an ASP.NET Core 2.x application. +budicon: 448 +topics: + - quickstarts + - webapp + - aspnet-core + - login +github: + path: Quickstart/01-Login +contentType: tutorial +useCase: quickstart +--- + + +<%= include('../../../_includes/_new_app', { showClientSecret: true, isPublicClient: false }) %> + +<%= include('./_includes/_setup') %> + +<%= include('./_includes/_login') %> diff --git a/articles/quickstart/webapp/aspnet-core-2/02-user-profile.md b/articles/quickstart/webapp/aspnet-core-2/02-user-profile.md new file mode 100644 index 0000000000..061b67e594 --- /dev/null +++ b/articles/quickstart/webapp/aspnet-core-2/02-user-profile.md @@ -0,0 +1,148 @@ +--- +title: User Profile +description: This tutorial will show you how to get the user's profile and display it. +budicon: 292 +topics: + - quickstarts + - webapp + - aspnet-core + - user-profile +github: + path: Quickstart/02-User-Profile +contentType: tutorial +useCase: quickstart +--- + + +## Get the Profile + +The OIDC middleware extracts the user's information from the ID Token and adds it as claims to the `ClaimsIdentity`. + +The seed project contains a controller action and view which display the claims associated with a user. Once a user has logged in, you can go to `/Account/Claims` to see these claims. + +You can create a custom user profile page for displaying a user's name, email address, and profile image. + +Create a view model containing the basic user profile information: + +```csharp +// ViewModels/UserProfileViewModel.cs + +public class UserProfileViewModel +{ + public string EmailAddress { get; set; } + + public string Name { get; set; } + + public string ProfileImage { get; set; } +} +``` + +Add a new `Profile` action to the `AccountController` and extract the relevant claims. Add the claims to a new instance of `UserProfileViewModel` passed to the view. Add the `[Authorize]` attribute to the action so only authenticated users can access the action: + +```csharp +// Controllers/AccountController.cs + +[Authorize] +public IActionResult Profile() +{ + 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 + }); +} +``` + +The `User.Identity.Name` property looks for a claim of a type `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name` on the user object. Auth0 passes the name of the user in the `name` claim of the ID Token, but this does not get automatically matched to the `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name` type. This means that `User.Identity.Name` will return null. + +You can control the claim type that ASP.NET Core retrieves when accessing the name through `User.Identity.Name`. To achieve this, update the OIDC authentication handler registration in the `Startup` class. Set the `NameClaimType` of the `TokenValidationParameters` property to `name`. ASP.NET Core will retrieve the value of the `name` claim passed in the ID Token when you access the name of the user with the `User.Identity.Name` property. + +You must update the list of scopes to request the `profile` scope. The user's profile information is returned as claims in the ID Token. + +```csharp +public void ConfigureServices(IServiceCollection services) +{ + // Some code omitted for brevity... + + // Add authentication services + services.AddAuthentication(options => { + //... + }) + .AddCookie() + .AddOpenIdConnect("Auth0", options => { + // ... + + // Configure the scope + options.Scope.Clear(); + options.Scope.Add("openid"); + options.Scope.Add("profile"); + options.Scope.Add("email"); + // Set the correct name claim type + options.TokenValidationParameters = new TokenValidationParameters + { + NameClaimType = "name" + }; + + //... + }); +} +``` + +Next create a view called `Profile.cshtml` inside `Views/Account` and display the user's name, email address and profile image. + +```html + + +@model SampleMvcApp.ViewModels.UserProfileViewModel +@{ + ViewData["Title"] = "User Profile"; +} + +
+
+
+

@ViewData["Title"].

+ +
+ +
+
+

@Model.Name

+

+ @Model.EmailAddress +

+
+
+
+
+``` + +Now when you log in and go to the URL `/Account/Profile`, you will see the user's profile with the information. + +## Display the User's Name in the Navigation Bar + +You can put a link in the top navigation bar to display the user's name. When the user clicks on their name, you can navigate them to their profile page. + +Go to the `Views/Shared/_Layout.cshtml` file and update the `Navbar` section to display the user's name and link to the `Profile` action in the `AccountController`: + +```html + + + +``` + +Now, after the user has logged in, the user's name is displayed in the top-right corner of the navigation bar: + +![](/media/articles/server-platforms/aspnet-core/navbar-userprofile.png) diff --git a/articles/quickstart/webapp/aspnet-core-2/03-authorization.md b/articles/quickstart/webapp/aspnet-core-2/03-authorization.md new file mode 100644 index 0000000000..28481c8abd --- /dev/null +++ b/articles/quickstart/webapp/aspnet-core-2/03-authorization.md @@ -0,0 +1,66 @@ +--- +title: Authorization +description: This tutorial will show you how to assign roles to your users, and use those claims to authorize or deny a user to access certain routes in the app. +budicon: 546 +topics: + - quickstarts + - webapp + - aspnet-core + - authorization +github: + path: Quickstart/03-Authorization +contentType: tutorial +useCase: quickstart +--- + + +ASP.NET Core supports [Role based Authorization](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/roles) which allows you to limit access to your application based on the user's role. This tutorial shows how to add role information to the user's ID token and then use it to limit access to your application. + +::: note +To follow the tutorial, make sure you are familiar with [Rules](/rules/current). +::: + +<%= include('../_includes/_create_and_assign_roles') %> + +## Restrict Access Based on User Roles + +Configure the OIDC authentication handler registration inside your ASP.NET application to inform it which claim in the ID Token contains the role information. Specify the `RoleClaimType` inside `TokenValidationParameters`. The value you specify must match the namespace you used in your rule. + +```csharp +public void ConfigureServices(IServiceCollection services) +{ + // Some code omitted for brevity... + + // Add authentication services + services.AddAuthentication(options => { + //... + }) + .AddCookie() + .AddOpenIdConnect("Auth0", options => { + // ... + + // Set the correct name claim type + options.TokenValidationParameters = new TokenValidationParameters + { + NameClaimType = "name", + RoleClaimType = "https://schemas.quickstarts.com/roles" + }; + + //... + }); +} +``` + +You can use the [Role based authorization](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/roles) mechanism to make sure that only the users with specific roles can access certain actions. Add the `[Authorize(Roles = ?)]` attribute to your controller action. + +The sample code below restricts the action only to users who have the `admin` role: + +```csharp +// Controllers/HomeController.cs + +[Authorize(Roles = "admin")] +public IActionResult Admin() +{ + return View(); +} +``` diff --git a/articles/quickstart/webapp/aspnet-core-2/_includes/_login.md b/articles/quickstart/webapp/aspnet-core-2/_includes/_login.md new file mode 100644 index 0000000000..bac70a7f26 --- /dev/null +++ b/articles/quickstart/webapp/aspnet-core-2/_includes/_login.md @@ -0,0 +1,380 @@ + + +## Configure Your Application to Use Auth0 + +[Universal Login](/hosted-pages/login) is the easiest way to set up authentication in your application. We recommend using it for the best experience, best security and the fullest array of features. This guide will use it to provide a way for your users to log in to your ASP.NET Core application. + +::: note +You can also create a custom login for prompting the user for their username and password. To learn how to do this in your application, follow the [Custom Login sample](https://github.com/auth0-samples/auth0-aspnetcore-mvc-samples/tree/master/Samples/custom-login). +::: + +### Install dependencies + +To integrate Auth0 with ASP.NET Core you will use the Cookie and OpenID Connect (OIDC) authentication handlers. The seed project already references the ASP.NET Core metapackage (`Microsoft.AspNetCore.App`) which includes **all** NuGet packages shipped by Microsoft as part of ASP.NET Core 2.1, including the packages for the Cookie and OIDC authentication handlers. + +If you are adding this to your own existing project, and you have not referenced the metapackage, then please make sure that you add the `Microsoft.AspNetCore.Authentication.Cookies` and `Microsoft.AspNetCore.Authentication.OpenIdConnect` packages to your application. + +```bash +Install-Package Microsoft.AspNetCore.Authentication.Cookies +Install-Package Microsoft.AspNetCore.Authentication.OpenIdConnect +``` + +### Install and configure OpenID Connect Middleware + +To enable authentication in your ASP.NET Core application, use the OpenID Connect (OIDC) middleware. +Go to the `ConfigureServices` method of your `Startup` class. To add the authentication services, call the `AddAuthentication` method. To enable cookie authentication, call the `AddCookie` method. + +Next, configure the OIDC authentication handler. Add a call to `AddOpenIdConnect`. To configure the authentication scheme, pass "Auth0" as the `authenticationScheme` parameter. You will use this value later to challenge the OIDC middleware. + +Configure other parameters, such as `ClientId`, `ClientSecret` or `ResponseType`. + +By default, the OIDC middleware requests both the `openid` and `profile` scopes. Because of that, you may get a large ID Token in return. We suggest that you ask only for the scopes you need. You can read more about requesting additional scopes in the [User Profile step](/quickstart/webapp/aspnet-core-2/02-user-profile). + +::: note +In the code sample below, only the `openid` scope is requested. +::: + +```cs +// Startup.cs + +public void ConfigureServices(IServiceCollection services) +{ + // Cookie configuration for HTTP to support cookies with SameSite=None + services.ConfigureSameSiteNoneCookies(); + + // Cookie configuration for HTTPS + // services.Configure(options => + // { + // options.MinimumSameSitePolicy = SameSiteMode.None; + // }); + + // Add authentication services + services.AddAuthentication(options => { + options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; + }) + .AddCookie() + .AddOpenIdConnect("Auth0", options => { + // Set the authority to your Auth0 domain + options.Authority = $"https://{Configuration["Auth0:Domain"]}"; + + // Configure the Auth0 Client ID and Client Secret + options.ClientId = Configuration["Auth0:ClientId"]; + options.ClientSecret = Configuration["Auth0:ClientSecret"]; + + // Set response type to code + options.ResponseType = OpenIdConnectResponseType.Code; + + // Configure the scope + options.Scope.Clear(); + options.Scope.Add("openid"); + + // Set the callback path, so Auth0 will call back to http://localhost:3000/callback + // Also ensure that you have added the URL as an Allowed Callback URL in your Auth0 dashboard + options.CallbackPath = new PathString("/callback"); + + // Configure the Claims Issuer to be Auth0 + options.ClaimsIssuer = "Auth0"; + }); + + // Add framework services. + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); +} +``` + +::: note +The `ConfigureSameSiteNoneCookies` method used above was added as part of the [sample application](https://github.com/auth0-samples/auth0-aspnetcore-mvc-samples/blob/netcore2.1/Quickstart/01-Login/Support/SameSiteServiceCollectionExtensions.cs) in order to ([make cookies with SameSite=None work over HTTP when using Chrome](https://blog.chromium.org/2019/10/developers-get-ready-for-new.html)). We recommend using HTTPS instead of HTTP, which removes the need for the `ConfigureSameSiteNoneCookies` method. +::: + +Next, add the authentication middleware. In the `Configure` method of the `Startup` class, call the `UseAuthentication` method. + +```csharp +// Startup.cs + +public void Configure(IApplicationBuilder app, IHostingEnvironment env) +{ + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseExceptionHandler("/Home/Error"); + app.UseHsts(); + } + + app.UseStaticFiles(); + app.UseCookiePolicy(); + + app.UseAuthentication(); + + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); +} + +} +``` + +## Trigger Authentication + +### Add the Login and Logout methods + +Add the `Login` and `Logout` actions to `AccountController`. + +To add the `Login` action, call `ChallengeAsync` and pass "Auth0" as the authentication scheme. This will invoke the OIDC authentication handler you registered in the `ConfigureServices` method. + +After the OIDC middleware signs the user in, the user is also automatically signed in to the cookie middleware. This allows the user to be authenticated on subsequent requests. + +For the `Logout` action, you need to sign the user out of both middlewares. + +The `RedirectUri` passed in both instances indicates where the user is redirected after they log in or fail to log in. + +```cs +// Controllers/AccountController.cs + +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; + +public class AccountController : Controller +{ + public async Task Login(string returnUrl = "/") + { + await HttpContext.ChallengeAsync("Auth0", new AuthenticationProperties() { RedirectUri = returnUrl }); + } + + [Authorize] + public async Task Logout() + { + await HttpContext.SignOutAsync("Auth0", new AuthenticationProperties + { + // Indicate here where Auth0 should redirect the user after a logout. + // Note that the resulting absolute Uri must be added to the + // **Allowed Logout URLs** settings for the app. + RedirectUri = Url.Action("Index", "Home") + }); + await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + } +} +``` + +ASP.NET Core calls `SignOutAsync` for the "Auth0" authentication scheme. You need to provide the OIDC middleware with the URL for logging the user out of Auth0. To set the URL, handle the `OnRedirectToIdentityProviderForSignOut` event when you register the OIDC authentication handler. + +When the application calls `SignOutAsync` for the OIDC middleware, it also calls the `/v2/logout` endpoint of the Auth0 Authentication API. The user is logged out of Auth0. + +If you specify the `returnTo` parameter, the users will be redirected there after they are logged out. Specify the URL for redirecting users in the **Allowed Logout URLs** field in your [Application Settings](${manage_url}/#/applications/${account.clientId}/settings). + +In the `Startup.cs` file, update the call to `AddOpenIdConnect` with the following code: + +```csharp +// Startup.cs + +public void ConfigureServices(IServiceCollection services) +{ + // Some code omitted for brevity... + + // Add authentication services + services.AddAuthentication(options => { + //... + }) + .AddCookie() + .AddOpenIdConnect("Auth0", options => { + // ... + + options.Events = new OpenIdConnectEvents + { + // handle the logout redirection + OnRedirectToIdentityProviderForSignOut = (context) => + { + var logoutUri = $"https://{Configuration["Auth0:Domain"]}/v2/logout?client_id={Configuration["Auth0:ClientId"]}"; + + var postLogoutUri = context.Properties.RedirectUri; + if (!string.IsNullOrEmpty(postLogoutUri)) + { + if (postLogoutUri.StartsWith("/")) + { + // transform to absolute + var request = context.Request; + postLogoutUri = request.Scheme + "://" + request.Host + request.PathBase + postLogoutUri; + } + logoutUri += $"&returnTo={ Uri.EscapeDataString(postLogoutUri)}"; + } + + context.Response.Redirect(logoutUri); + context.HandleResponse(); + + return Task.CompletedTask; + } + }; + }); +} +``` + +### Add the Login and Logout buttons + +Add the **Login** and **Logout** buttons to the navigation bar. In the `/Views/Shared/_Layout.cshtml` file, in the navigation bar section, add code that displays the **Log Out** button when the user is authenticated and the **Login** button if not. The buttons link to the `Logout` and `Login` actions in the `AccountController`: + +```html + + + +``` + +### Run the Application + +When the user selects the **Login** button, the OIDC middleware redirects them to the hosted version of the [Lock](/libraries/lock/v10/customization) widget in your Auth0 domain. + +#### About the login flow + +1. The user clicks on the **Login** button and is directed to the `Login` route. +2. The `ChallengeAsync` tells the ASP.NET authentication middleware to issue a challenge to the authentication handler registered with the Auth0 `authenticationScheme` parameter. The parameter uses the "Auth0" value you passed in the call to `AddOpenIdConnect` in the `Startup` class. +3. The OIDC handler redirects the user to the Auth0 `/authorize` endpoint, which displays the Lock widget. The user can log in with their username and password, social provider or any other identity provider. +4. Once the user has logged in, Auth0 calls back to the `/callback` endpoint in your application and passes along an authorization code. +5. The OIDC handler intercepts requests made to the `/callback` path. +6. The handler looks for the authorization code, which Auth0 sent in the query string. +7. The OIDC handler calls the `/oauth/token` endpoint to exchange the authorization code for the user's ID and Access Tokens. +8. The OIDC middleware extracts the user information from the claims on the ID Token. +9. The OIDC middleware returns a successful authentication response and a cookie which indicates that the user is authenticated. The cookie contains claims with the user's information. The cookie is stored, so that the cookie middleware will automatically authenticate the user on any future requests. The OIDC middleware receives no more requests, unless it is explicitly challenged. + +## Obtain an Access Token for Calling an API + +If you want to call an API from your MVC application, you need to obtain an Access Token issued for the API you want to call. To obtain the token, pass an additional `audience` parameter containing the API identifier to the Auth0 authorization endpoint. + +In the configuration for the `OpenIdConnectOptions` object, handle the `OnRedirectToIdentityProvider` event and add the `audience` parameter to `ProtocolMessage`. + +```csharp +// Startup.cs + +public void ConfigureServices(IServiceCollection services) +{ + // Some code omitted for brevity... + + services.AddAuthentication(options => { + //... + }) + .AddCookie() + .AddOpenIdConnect("Auth0", options => { + // ... + + options.Events = new OpenIdConnectEvents + { + OnRedirectToIdentityProvider = context => + { + // The context's ProtocolMessage can be used to pass along additional query parameters + // to Auth0's /authorize endpoint. + // + // Set the audience query parameter to the API identifier to ensure the returned Access Tokens can be used + // to call protected endpoints on the corresponding API. + context.ProtocolMessage.SetParameter("audience", Configuration["Auth0:Audience"]); + + return Task.FromResult(0); + } + }; + }); +} +``` + +Be sure to also update your application's `appsettings.json` file to include the Audience configuration: + +``` xml +"Auth0": { + ... + "Audience": "${apiIdentifier}" +} +``` + +### Store and retrieve the tokens + +The OIDC middleware in ASP.NET Core automatically decodes the ID Token returned from Auth0 and adds the claims from the ID Token as claims in the `ClaimsIdentity`. This means that you can use `User.Claims.FirstOrDefault("").Value` to obtain the value of any claim inside any action in your controllers. + +The seed project contains a controller action and view that display the claims associated with a user. Once a user has logged in, you can go to `/Account/Claims` to see these claims. + +You may want to store the tokens received from Auth0. For example, you can use the Access Token later to authenticate the user in calls to your API. To achieve this, when calling `AddOpenIdConnect`, set the `SaveTokens` property to `true`. This saves the tokens to `AuthenticationProperties`: + +```csharp +// Startup.cs + +public void ConfigureServices(IServiceCollection services) +{ + // Some code omitted for brevity... + + // Add authentication services + services.AddAuthentication(options => { + //... + }) + .AddCookie() + .AddOpenIdConnect("Auth0", options => { + // ... + + // Saves tokens to the AuthenticationProperties + options.SaveTokens = true; + + options.Events = new OpenIdConnectEvents + { + // handle the logout redirection + OnRedirectToIdentityProviderForSignOut = (context) => + { + //... + } + }; + }); +} +``` + +To retrieve the tokens, call `HttpContext.GetTokenAsync` and use them as required: + +```csharp +// Inside one of your controller actions + +if (User.Identity.IsAuthenticated) +{ + string accessToken = await HttpContext.GetTokenAsync("access_token"); + + // if you need to check the Access Token expiration time, use this value + // provided on the authorization response and stored. + // do not attempt to inspect/decode the access token + DateTime accessTokenExpiresAt = DateTime.Parse( + await HttpContext.GetTokenAsync("expires_at"), + CultureInfo.InvariantCulture, + DateTimeStyles.RoundtripKind); + + string idToken = await HttpContext.GetTokenAsync("id_token"); + + // Now you can use them. For more info on when and how to use the + // Access Token and ID Token, see https://auth0.com/docs/tokens +} +``` + +For general information on using APIs with web applications, see the [Authorization Code Flow](/flows/concepts/auth-code) article. diff --git a/articles/quickstart/webapp/aspnet-core-2/_includes/_setup.md b/articles/quickstart/webapp/aspnet-core-2/_includes/_setup.md new file mode 100644 index 0000000000..4d0233b199 --- /dev/null +++ b/articles/quickstart/webapp/aspnet-core-2/_includes/_setup.md @@ -0,0 +1,17 @@ + + +## Configure Callback URLs + +The Callback URL of your application is the URL where Auth0 will redirect to after the user has authenticated in order for the OpenID Connect middleware to complete the authentication process. + +You will need to add this URL to the list of Allowed URLs for your application. The Callback URL for the seed project is `http://localhost:3000/callback`, so be sure to add this to the **Allowed Callback URLs** section of your application. + +If you deploy your application to a different URL you will also need to ensure to add that URL to the **Allowed Callback URLs**. For ASP.NET Core this URL will take the format `https://YOUR_APPLICATION_URL/callback`. + +<%= include('../../../../_includes/_logout_url', { returnTo: 'http://localhost:3000' }) %> + +### Configure JSON Web Token signature algorithm + +The ASP.NET Core OpenID Connect (OIDC) middleware which will be used to authenticate the user, requires that the JSON Web Token (JWT) be signed with an asymmetric key. To configure this go to the settings for your application in the Auth0 Dashboard, scroll down and click on **Show Advanced Settings**. Go to the **OAuth** tab and set the **JsonWebToken Signature Algorithm** to **RS256**. + +Save your changes. diff --git a/articles/quickstart/webapp/aspnet-core-2/download.md b/articles/quickstart/webapp/aspnet-core-2/download.md new file mode 100644 index 0000000000..49f7a98084 --- /dev/null +++ b/articles/quickstart/webapp/aspnet-core-2/download.md @@ -0,0 +1,30 @@ + + +To run the sample follow these steps: + +1) Set the **Allowed Callback URLs** in the [Application Settings](${manage_url}/#/applications/${account.clientId}/settings) to: + +```text +http://localhost:3000/callback +``` + +2) Set the **Allowed Logout URLs** in the [Application Settings](${manage_url}/#/applications/${account.clientId}/settings) to: + +```text +http://localhost:3000 +``` + +3) Make sure [.NET Core](https://www.microsoft.com/net/download) is installed, and run the following commands: + +```bash +dotnet run +``` + +You can also run it from a [Docker](https://www.docker.com) image with the following commands: + +```bash +# In Linux / macOS +sh exec.sh +# In Windows' Powershell +./exec.ps1 +``` diff --git a/articles/quickstart/webapp/aspnet-core-2/index.yml b/articles/quickstart/webapp/aspnet-core-2/index.yml new file mode 100644 index 0000000000..f2dab0a764 --- /dev/null +++ b/articles/quickstart/webapp/aspnet-core-2/index.yml @@ -0,0 +1,46 @@ +title: ASP.NET Core v2.1 +# TODO remove 'image' once new QS page is live. Then only use 'logo'. +image: /media/platforms/asp.png +logo: dotnet +author: + name: Damien Guard + email: damien.guard@auth0.com + community: false +topics: + - quickstart +contentType: tutorial +useCase: quickstart +snippets: + dependencies: server-platforms/aspnet-core/dependencies +alias: + - aspnetcore-2.1 +seo_alias: aspnet-core +default_article: 01-login +articles: + - 01-login + - 02-user-profile + - 03-authorization +show_steps: true +github: + org: auth0-samples + repo: auth0-aspnetcore-mvc-samples + branch: netcore2.1 +requirements: + - .NET Core 2.1 +sample_download_required_data: + - client +next_steps: + - path: 03-authorization + list: + - text: Configure other identity providers + icon: 345 + href: "/identityproviders" + - text: Enable multifactor authentication + icon: 345 + href: "/multifactor-authentication" + - text: Learn about attack protection + icon: 345 + href: "/attack-protection" + - text: Learn about rules + icon: 345 + href: "/rules" diff --git a/config/redirects.js b/config/redirects.js index 839a6189bf..c8f31ef261 100644 --- a/config/redirects.js +++ b/config/redirects.js @@ -333,12 +333,11 @@ const redirects = [ '/quickstart/backend/aspnet-core-webapi/06-authorization-deprecated', '/quickstart/backend/aspnet-core-webapi/06-authorization-legacy', '/quickstart/backend/aspnet-core-webapi/00-getting-started', - '/quickstart/backend/aspnet-core-webapi-2', ], to: '/quickstart/backend/aspnet-core-webapi', }, { - from: ['/quickstart/webapp/aspnet-core-3', '/quickstart/webapp/aspnet-core-2'], + from: '/quickstart/webapp/aspnet-core-3', to: '/quickstart/webapp/aspnet-core', }, { @@ -393,8 +392,10 @@ const redirects = [ to: '/quickstart/webapp/express', }, { - from: ['/quickstart/native/cordova'], - to: '/quickstart', + from: [ + '/quickstart/native/cordova', + ], + to: '/quickstart' }, /* CONNECTIONS */ @@ -955,11 +956,16 @@ const redirects = [ /* ARCHITECTURE SCENARIOS */ { - from: ['/architecture-scenarios'], + from: [ + '/architecture-scenarios', + ], to: '/get-started/architecture-scenarios', }, { - from: ['/architecture-scenarios/application/mobile-api', '/architecture-scenarios/mobile-api'], + from: [ + '/architecture-scenarios/application/mobile-api', + '/architecture-scenarios/mobile-api' + ], to: '/get-started/architecture-scenarios/mobile-api', }, { @@ -1576,6 +1582,7 @@ const redirects = [ { from: [`/get-started/applications/how-to-rotate-application-secret`], to: `/get-started/applications/rotate-client-secret`, + }, { from: [ @@ -1605,7 +1612,7 @@ const redirects = [ '/get-started/dashboard/rotate-client-secret', '/applications/rotate-client-secret', '/configure/applications/rotate-client-secret', - '/applications/how-to-rotate-application-secret', + '/applications/how-to-rotate-application-secret' ], to: '/get-started/applications/rotate-client-secret', }, @@ -2333,26 +2340,38 @@ const redirects = [ to: '/customize/actions/flows-and-triggers', }, { - from: ['/actions/manage-action-versions', '/actions/manage-versions', '/customize/actions/manage-action-versions'], + from: [ + '/actions/manage-action-versions', + '/actions/manage-versions', + '/customize/actions/manage-action-versions' + ], to: '/customize/actions/manage-versions', }, { - from: ['/actions/triggers/send-phone-message', '/customize/actions/triggers/send-phone-message'], + from: [ + '/actions/triggers/send-phone-message', + '/customize/actions/triggers/send-phone-message' + ], to: '/customize/actions/flows-and-triggers/send-phone-message-flow', }, { from: [ '/actions/triggers/send-phone-message/event-object', - '/customize/actions/triggers/send-phone-message/event-object', - ], + '/customize/actions/triggers/send-phone-message/event-object' + ], to: '/customize/actions/flows-and-triggers/send-phone-message-flow/event-object', }, { - from: ['/actions/programming-model-changes', '/customize/actions/programming-model-changes'], + from: [ + '/actions/programming-model-changes', + '/customize/actions/programming-model-changes' + ], to: '/customize/actions/migrate/migrate-from-actions-beta-to-final', }, { - from: ['/customize/actions/migrate-from-rules-to-actions'], + from: [ + '/customize/actions/migrate-from-rules-to-actions' + ], to: '/customize/actions/migrate/migrate-from-rules-to-actions', }, { @@ -2360,7 +2379,10 @@ const redirects = [ to: '/customize/actions/limitations', }, { - from: ['/actions/triggers/post-change-password', '/customize/actions/triggers/post-change-password'], + from: [ + '/actions/triggers/post-change-password', + '/customize/actions/triggers/post-change-password' + ], to: '/customize/actions/flows-and-triggers/post-change-password-flow', }, { @@ -2371,68 +2393,86 @@ const redirects = [ to: '/customize/actions/flows-and-triggers/post-change-password-flow/event-object', }, { - from: ['/actions/triggers/post-user-registration', '/customize/actions/triggers/post-user-registration'], + from: [ + '/actions/triggers/post-user-registration', + '/customize/actions/triggers/post-user-registration' + ], to: '/customize/actions/flows-and-triggers/post-user-registration-flow', }, { from: [ '/actions/triggers/post-user-registration/event-object', - '/customize/actions/triggers/post-user-registration/event-object', + '/customize/actions/triggers/post-user-registration/event-object' ], to: '/customize/actions/flows-and-triggers/post-user-registration-flow/event-object', }, { - from: ['/actions/triggers/pre-user-registration', '/customize/actions/triggers/pre-user-registration'], + from: [ + '/actions/triggers/pre-user-registration', + '/customize/actions/triggers/pre-user-registration' + ], to: '/customize/actions/flows-and-triggers/pre-user-registration-flow', }, { from: [ '/actions/triggers/pre-user-registration/event-object', - '/customize/actions/triggers/pre-user-registration/event-object', + '/customize/actions/triggers/pre-user-registration/event-object' ], to: '/customize/actions/flows-and-triggers/pre-user-registration-flow/event-object', }, { from: [ '/actions/triggers/pre-user-registration/api-object', - '/customize/actions/triggers/pre-user-registration/api-object', + '/customize/actions/triggers/pre-user-registration/api-object' ], to: '/customize/actions/flows-and-triggers/pre-user-registration-flow/api-object', }, { - from: ['/actions/triggers/credentials-exchange', '/customize/actions/triggers/credentials-exchange'], + from: [ + '/actions/triggers/credentials-exchange', + '/customize/actions/triggers/credentials-exchange' + ], to: '/customize/actions/flows-and-triggers/machine-to-machine-flow', }, { from: [ '/actions/triggers/credentials-exchange/event-object', - '/customize/actions/triggers/credentials-exchange/event-object', + '/customize/actions/triggers/credentials-exchange/event-object' ], to: '/customize/actions/flows-and-triggers/machine-to-machine-flow/event-object', }, { from: [ '/actions/triggers/credentials-exchange/api-object', - '/customize/actions/triggers/credentials-exchange/api-object', + '/customize/actions/triggers/credentials-exchange/api-object' ], to: '/customize/actions/flows-and-triggers/machine-to-machine-flow/api-object', }, { - from: ['/actions/triggers/post-login', '/customize/actions/triggers/post-login'], + from: [ + '/actions/triggers/post-login', + '/customize/actions/triggers/post-login' + ], to: '/customize/actions/flows-and-triggers/login-flow', }, { - from: ['/actions/triggers/post-login/event-object', '/customize/actions/triggers/post-login/event-object'], + from: [ + '/actions/triggers/post-login/event-object', + '/customize/actions/triggers/post-login/event-object' + ], to: '/customize/actions/flows-and-triggers/login-flow/event-object', }, { - from: ['/actions/triggers/post-login/api-object', '/customize/actions/triggers/post-login/api-object'], + from: [ + '/actions/triggers/post-login/api-object', + '/customize/actions/triggers/post-login/api-object' + ], to: '/customize/actions/flows-and-triggers/login-flow/api-object', }, { from: [ '/actions/triggers/post-login/redirect-with-actions', - '/customize/actions/triggers/post-login/redirect-with-actions', + '/customize/actions/triggers/post-login/redirect-with-actions' ], to: '/customize/actions/flows-and-triggers/login-flow/redirect-with-actions', }, @@ -3038,7 +3078,11 @@ const redirects = [ to: '/troubleshoot/performance-best-practices', }, { - from: ['/best-practices/rules', '/best-practices/rules-best-practices', '/customize/rules/rules-best-practices'], + from: [ + '/best-practices/rules', + '/best-practices/rules-best-practices', + '/customize/rules/rules-best-practices', + ], to: '/customize/actions/action-coding-guidelines', }, { @@ -3046,6 +3090,7 @@ const redirects = [ '/best-practices/search-best-practices', '/users/search/best-practices', '/best-practices/user-search-best-practices', + ], to: '/manage-users/user-search/user-search-best-practices', }, @@ -3053,14 +3098,14 @@ const redirects = [ from: [ '/best-practices/rules-best-practices/rules-anatomy-best-practices', '/customize/rules/rules-best-practices/rules-anatomy-best-practices', - ], + ], to: '/customize/actions/action-coding-guidelines', }, { from: [ '/best-practices/rules-best-practices/rules-environment-best-practices', '/customize/rules/rules-best-practices/rules-environment-best-practices', - ], + ], to: '/customize/actions/action-coding-guidelines', }, { @@ -3079,7 +3124,7 @@ const redirects = [ }, { from: [ - '/best-practices/testing', + '/best-practices/testing', '/best-practices/rules-best-practices/rules-testing-best-practices', '/customize/rules/rules-best-practices/rules-testing-best-practices', ], @@ -3380,7 +3425,7 @@ const redirects = [ '/brand-and-customize/customize-login-text-prompts/prompt-device-flow', '/customize/universal-login-pages/customize-login-text-prompts/prompt-device-flow', ], - to: '/customize/universal-login-pages/customize-login-text-prompts', + to: '/customize/universal-login-pages/customize-login-text-prompts' }, { from: [ @@ -4023,54 +4068,78 @@ const redirects = [ to: '/customize/extensions/single-sign-on-dashboard-extension/update-applications-on-the-sso-dashboard', }, { - from: ['/deploy/private-cloud-on-azure'], + from: [ + '/deploy/private-cloud-on-azure', + ], to: '/deploy-monitor/deploy-private-cloud/private-cloud-on-azure', }, /* Deploy CLI Tool */ - + { - from: ['/deploy-monitor/deploy-cli-tool/install-and-configure-the-deploy-cli-tool'], + from: [ + '/deploy-monitor/deploy-cli-tool/install-and-configure-the-deploy-cli-tool', + ], to: '/deploy-monitor/deploy-cli-tool', }, { - from: ['/deploy-monitor/deploy-cli-tool/create-and-configure-the-deploy-cli-application'], + from: [ + '/deploy-monitor/deploy-cli-tool/create-and-configure-the-deploy-cli-application', + ], to: '/deploy-monitor/deploy-cli-tool', }, { - from: ['/deploy-monitor/auth0-deploy-cli/configuring-the-deploy-cli'], + from: [ + '/deploy-monitor/auth0-deploy-cli/configuring-the-deploy-cli', + ], to: '/deploy-monitor/deploy-cli-tool/configuring-the-deploy-cli', }, { - from: ['/deploy-monitor/deploy-cli-tool/call-deploy-cli-tool-programmatically'], + from: [ + '/deploy-monitor/deploy-cli-tool/call-deploy-cli-tool-programmatically', + ], to: '/deploy-monitor/deploy-cli-tool/using-as-a-node-module', }, { - from: ['/deploy-monitor/deploy-cli-tool/incorporate-deploy-cli-into-build-environment'], + from: [ + '/deploy-monitor/deploy-cli-tool/incorporate-deploy-cli-into-build-environment', + ], to: '/deploy-monitor/deploy-cli-tool/incorporating-into-multi-environment-workflows', }, { - from: ['/deploy-monitor/deploy-cli-tool/import-export-tenant-configuration-to-yaml-file'], + from: [ + '/deploy-monitor/deploy-cli-tool/import-export-tenant-configuration-to-yaml-file', + ], to: '/deploy-monitor/deploy-cli-tool/keyword-replacement', }, { - from: ['/deploy-monitor/deploy-cli-tool/import-export-tenant-configuration-to-directory-structure'], + from: [ + '/deploy-monitor/deploy-cli-tool/import-export-tenant-configuration-to-directory-structure', + ], to: '/deploy-monitor/deploy-cli-tool/keyword-replacement', - }, + }, { - from: ['/deploy-monitor/deploy-cli-tool/environment-variables-and-keyword-mappings'], + from: [ + '/deploy-monitor/deploy-cli-tool/environment-variables-and-keyword-mappings', + ], to: '/deploy-monitor/deploy-cli-tool/keyword-replacement', }, { - from: ['/deploy-monitor/deploy-cli-tool/deploy-cli-tool-options'], + from: [ + '/deploy-monitor/deploy-cli-tool/deploy-cli-tool-options', + ], to: '/deploy-monitor/deploy-cli-tool/using-as-a-cli', }, { - from: ['/deploy-monitor/deploy-cli-tool/auth0-terraform-provider'], + from: [ + '/deploy-monitor/deploy-cli-tool/auth0-terraform-provider' + ], to: '/deploy-monitor/auth0-terraform-provider', }, { - from: ['/deploy-monitor/deploy-cli-tool/how-to-contribute'], + from: [ + '/deploy-monitor/deploy-cli-tool/how-to-contribute', + ], to: '/deploy-monitor/deploy-cli-tool', }, @@ -4437,8 +4506,8 @@ const redirects = [ }, { from: [ - '/extensions/authentication-api-webhooks', - '/extensions/auth0-authentication-api-webhooks', + '/extensions/authentication-api-webhooks', + '/extensions/auth0-authentication-api-webhooks' , '/customize/extensions/auth0-authentication-api-webhooks', ], to: '/customize/extensions', @@ -4471,7 +4540,7 @@ const redirects = [ }, { from: [ - '/extensions/management-api-webhooks', + '/extensions/management-api-webhooks', '/extensions/auth0-management-api-webhooks', '/customize/extensions/auth0-management-api-webhooks', ], @@ -4911,9 +4980,8 @@ const redirects = [ to: '/customize/hooks/view-hooks', }, { - from: [ - '/hooks/extensibility-points/send-phone-message', - '/customize/hooks/extensibility-points/send-phone-message', + from: ['/hooks/extensibility-points/send-phone-message', + '/customize/hooks/extensibility-points/send-phone-message', ], to: '/customize/actions/flows-and-triggers', }, @@ -4921,23 +4989,38 @@ const redirects = [ /* Identity Labs */ { - from: ['/labs', '/identity-labs'], + from: [ + '/labs', + '/identity-labs', + ], to: '/get-started', }, { - from: ['/identity-labs/01-web-sign-in', '/identity-labs/lab-1-web-sign-in'], + from: [ + '/identity-labs/01-web-sign-in', + '/identity-labs/lab-1-web-sign-in', + ], to: '/get-started', }, { - from: ['/identity-labs/01-web-sign-in/exercise-01', '/identity-labs/lab-1-web-sign-in/identity-lab-1-exercise-1'], + from: [ + '/identity-labs/01-web-sign-in/exercise-01', + '/identity-labs/lab-1-web-sign-in/identity-lab-1-exercise-1', + ], to: '/get-started', }, { - from: ['/identity-labs/01-web-sign-in/exercise-02', '/identity-labs/lab-1-web-sign-in/identity-lab-1-exercise-2'], + from: [ + '/identity-labs/01-web-sign-in/exercise-02', + '/identity-labs/lab-1-web-sign-in/identity-lab-1-exercise-2', + ], to: '/get-started', }, { - from: ['/identity-labs/02-calling-an-api', '/identity-labs/identity-lab-2-calling-api'], + from: [ + '/identity-labs/02-calling-an-api', + '/identity-labs/identity-lab-2-calling-api', + ], to: '/get-started', }, { @@ -4945,7 +5028,7 @@ const redirects = [ '/identity-labs/02-calling-an-api/exercise-01', '/identity-labs/identity-lab-2-calling-api/identity-lab-2-exercise-1', ], - to: '/get-started', + to: '/get-started', }, { from: [ @@ -4962,7 +5045,10 @@ const redirects = [ to: '/get-started', }, { - from: ['/identity-labs/03-mobile-native-app', '/identity-labs/lab-3-mobile-native-app'], + from: [ + '/identity-labs/03-mobile-native-app', + '/identity-labs/lab-3-mobile-native-app', + ], to: '/get-started', }, { @@ -4987,7 +5073,10 @@ const redirects = [ to: '/get-started', }, { - from: ['/identity-labs/04-single-page-app', '/identity-labs/lab-4-single-page-app'], + from: [ + '/identity-labs/04-single-page-app', + '/identity-labs/lab-4-single-page-app', + ], to: '/get-started', }, { @@ -6057,12 +6146,16 @@ const redirects = [ to: '/authenticate/login/logout/redirect-users-after-logout', }, { - from: ['/back-channel-logout'], - to: '/authenticate/login/logout/back-channel-logout', + from: [ + '/back-channel-logout' + ], + to: '/authenticate/login/logout/back-channel-logout' }, { - from: ['/configure-back-channel-logout'], - to: '/authenticate/login/logout/back-channel-logout/configure-back-channel-logout', + from: [ + '/configure-back-channel-logout' + ], + to: '/authenticate/login/logout/back-channel-logout/configure-back-channel-logout' }, /* Monitor - Logs */ @@ -6346,12 +6439,14 @@ const redirects = [ '/mfa/configure-telesign-as-mfa-sms-provider', '/login/mfa/mfa-factors/configure-telesign-as-mfa-sms-provider', '/secure/multi-factor-authentication/multi-factor-authentication-factors/configure-telesign-as-mfa-sms-provider', - '/customize/hooks/extensibility-points/send-phone-message/configure-telesign-as-mfa-sms-provider', + '/customize/hooks/extensibility-points/send-phone-message/configure-telesign-as-mfa-sms-provider' ], to: '/customize/actions/flows-and-triggers', }, { - from: ['/mfa/webauthn-as-mfa'], + from: [ + '/mfa/webauthn-as-mfa' + ], to: '/secure/multi-factor-authentication/webauthn-as-mfa', }, { @@ -6361,7 +6456,7 @@ const redirects = [ '/mfa/configure-twilio-as-mfa-sms-provider', '/login/mfa/mfa-factors/configure-twilio-as-mfa-sms-provider', '/secure/multi-factor-authentication/multi-factor-authentication-factors/configure-twilio-as-mfa-sms-provider', - '/customize/hooks/extensibility-points/send-phone-message/configure-twilio-as-mfa-sms-provider', + '/customize/hooks/extensibility-points/send-phone-message/configure-twilio-as-mfa-sms-provider' ], to: '/customize/actions/flows-and-triggers', }, @@ -6372,7 +6467,7 @@ const redirects = [ '/mfa/configure-vonage-as-mfa-sms-provider', '/login/mfa/mfa-factors/configure-vonage-as-mfa-sms-provider', '/secure/multi-factor-authentication/multi-factor-authentication-factors/configure-vonage-as-mfa-sms-provider', - '/customize/hooks/extensibility-points/send-phone-message/configure-vonage-as-mfa-sms-provider', + '/customize/hooks/extensibility-points/send-phone-message/configure-vonage-as-mfa-sms-provider' ], to: '/customize/actions/flows-and-triggers', }, @@ -6423,7 +6518,7 @@ const redirects = [ }, { from: '/secure/multi-factor-authentication/adaptive-mfa/adaptive-mfa-rule-actions', - to: '/secure/multi-factor-authentication/adaptive-mfa/adaptive-mfa-rules', + to: '/secure/multi-factor-authentication/adaptive-mfa/adaptive-mfa-rules' }, { from: ['/mfa/adaptive-mfa/adaptive-mfa-log-events', '/login/mfa/adaptive-mfa/adaptive-mfa-log-events'], @@ -6935,7 +7030,11 @@ const redirects = [ to: '/get-started/professional-services/maintain-improve', }, { - from: ['/services/packages', '/professional-services/packages', '/troubleshoot/professional-services/packages'], + from: [ + '/services/packages', + '/professional-services/packages', + '/troubleshoot/professional-services/packages', + ], to: '/get-started/professional-services/packages', }, @@ -6961,7 +7060,7 @@ const redirects = [ '/rules/automatically-generate-leads-in-shopify', '/customize/rules/use-cases/automatically-generate-leads-in-shopify', ], - to: '/customize/actions/use-cases', + to: '/customize/actions/use-cases', }, { from: ['/rules/guides/cache-resources', '/rules/cache-expensive-resources-in-rules', '/rules/cache-resources'], @@ -6999,12 +7098,16 @@ const redirects = [ to: '/customize/rules/debug-rules', }, { - from: ['/rules/references/samples', '/rules/examples', '/customize/rules/examples'], + from: [ + '/rules/references/samples', + '/rules/examples', + '/customize/rules/examples', + ], to: '/customize/actions/actions-overview', }, { from: [ - '/rules/guides/integrate-user-id-verification', + '/rules/guides/integrate-user-id-verification', '/rules/integrate-user-id-verification', '/customize/rules/use-cases/integrate-user-id-verification', ], @@ -7012,7 +7115,7 @@ const redirects = [ }, { from: [ - '/rules/guides/integrate-efm-solutions', + '/rules/guides/integrate-efm-solutions', '/rules/integrate-efm-solutions', '/customize/rules/use-cases/integrate-efm-solutions', ], @@ -7028,43 +7131,47 @@ const redirects = [ }, { from: [ - '/rules/guides/integrate-hubspot', + '/rules/guides/integrate-hubspot', '/rules/integrate-hubspot', '/customize/rules/use-cases/integrate-hubspot', ], - to: '/customize/actions/use-cases', + to: '/customize/actions/use-cases', }, { from: [ - '/rules/guides/integrate-maxmind', + '/rules/guides/integrate-maxmind', '/rules/integrate-maxmind', '/customize/rules/use-cases/integrate-maxmind', ], - to: '/customize/actions/use-cases', + to: '/customize/actions/use-cases', }, { from: [ - '/rules/guides/integrate-mixpanel', + '/rules/guides/integrate-mixpanel', '/rules/integrate-mixpanel', '/customize/rules/use-cases/integrate-mixpanel', ], - to: '/customize/actions/use-cases', + to: '/customize/actions/use-cases', }, { from: [ - '/rules/guides/integrate-salesforce', + '/rules/guides/integrate-salesforce', '/rules/integrate-salesforce', - '/customize/rules/use-cases/integrate-salesforce', + '/customize/rules/use-cases/integrate-salesforce', ], - to: '/customize/actions/use-cases', + to: '/customize/actions/use-cases', }, - { + { from: ['/rules/current/redirect', '/rules/redirect', '/rules/guides/redirect', '/rules/redirect-users'], to: '/customize/rules/redirect-users', }, { - from: ['/rules/references/use-cases', '/rules/use-cases', '/customize/rules/use-cases'], - to: '/customize/actions/use-cases', + from: [ + '/rules/references/use-cases', + '/rules/use-cases', + '/customize/rules/use-cases', + ], + to: '/customize/actions/use-cases', }, { from: ['/rules/current/management-api', '/rules/guides/management-api', '/rules/use-management-api'], @@ -7084,7 +7191,7 @@ const redirects = [ '/rules/use-cases/track-new-leads-in-salesforce', '/customize/rules/use-cases/track-new-leads-in-salesforce', ], - to: '/customize/actions/use-cases', + to: '/customize/actions/use-cases', }, { from: [ @@ -7096,7 +7203,7 @@ const redirects = [ '/rules/use-cases/track-new-sign-ups-in-salesforce', '/customize/rules/use-cases/track-new-sign-ups-in-salesforce', ], - to: '/customize/actions/use-cases', + to: '/customize/actions/use-cases', }, { from: ['/rules/raise-errors-from-rules'], @@ -7685,7 +7792,7 @@ const redirects = [ '/support/support-procedures', '/troubleshoot/customer-support/support-procedures', '/support/support-hours-and-languages', - '/troubleshoot/customer-support/support-hours-and-languages', + '/troubleshoot/customer-support/support-hours-and-languages' ], to: '/troubleshoot/customer-support/support-plans', },