Authenticate Next.js SPA with ASP.NET 6 Identity and Duende Identity Server Part 1
Tags: aspnet, swagger, nextjs, duendeidentityserver
Published: 2022-03-30 09:15:17

---
title: Authenticate Next.js SPA with ASP.NET 6 Identity and Duende Identity Server Part 1
published: true
description: Tutorial provides step by step to create an API Application and protect it with Duende Identity Server. We are going to implement authorization for Swagger UI and a Next.js SPA application
tags: ASPNET, Swagger, NextJS, DuendeIdentityServer
cover_image: https://res.cloudinary.com/baonguyen-dev/image/upload/v1648628415/large_joshua_hoehne_k_Xzr_YQ_A7_Tk_unsplash_65d514498c.jpg
---

This article was originally posted at my [Blog Page](https://www.baonguyen.dev/blog/authenticate-next-js-spa-with-asp-net-6-identity-and-duende-identity-server-part-1)

## The goal
In this tutorial, we are going to provide step by step to create an API Application and protect it with Duende Identity Server. We are going to implement authorization for Swagger UI and a Next.js SPA application.

## Github Repo
To easily follow along with this post, you can look into the Github repo:
[SPA Identity Server Authenticate Sample](https://github.com/thanhbao0408/SPA-Identity-Server-Authenticate-Sample)

## Solution Structure
Our applications will contain these projects.

![Next.js Duende Identity Server Protect API Structure](https://res.cloudinary.com/baonguyen-dev/image/upload/v1648547575/Next.js%20SPA%2C%20Identity%20Server/solution_structure_kumofd.png)


## Authentication Flows
We have 3 authorization flows. In this part of the tutorial, we will cover Flow 1 and Flow 2.

### Flow 1: Using Swagger UI
![Flow: Using Swagger UI](https://res.cloudinary.com/baonguyen-dev/image/upload/v1648547576/Next.js%20SPA%2C%20Identity%20Server/flow_1_b26o3l.png)

### Flow 2: Using Next.js SPA and Identity Server MVC UI

![Flow: Using Next.js SPA and Identity Server MVC UI](https://res.cloudinary.com/baonguyen-dev/image/upload/v1648547579/Next.js%20SPA%2C%20Identity%20Server/flow_2_e1ctai.png)

### Flow 3: Using Next.js SPA to request authorization directly from Identity Server APIs
![Flow 3: Using Next.js SPA to request authorizattion directly from Identity Server APIs](https://res.cloudinary.com/baonguyen-dev/image/upload/v1648548150/Next.js%20SPA%2C%20Identity%20Server/flow_3png_eak7wa.png)

## Prerequisites
- .NET SDK: .NET 6.0 (recommended): [https://dotnet.microsoft.com/en-us/download/dotnet/6.0](https://dotnet.microsoft.com/en-us/download/dotnet/6.0)
- Node.js: 16.14.2 LTS (recommended): [https://nodejs.org/en/](https://nodejs.org/en/)
- (Optional) yarn: You can use yarn as an alternative to npm: [https://yarnpkg.com/getting-started/install](https://yarnpkg.com/getting-started/install)
- NextJs CLI: [https://nextjs.org/docs/api-reference/cli](https://nextjs.org/docs/api-reference/cli)
- Your favourite Text IDE

## API Project - The resource we need to protect

### Create the Solution and the WebAPI project

```bash
# Create the folder and navigate into it
mkdir spa_identity_server_authenticate_sample && cd spa_identity_server_authenticate_sample

# Create a new solution to contain our projects
dotnet new sln

# create a new project named WebApiDemo
dotnet new webapi -n WebApiDemo

# Add the created project to our solution
dotnet sln add WebApiDemo\WebApiDemo.csproj
```
This will create a sample API project for us named **WebApiDemo**. Which already contains an endpoint `GET /WeatherForecast` inside the **WeatherController.cs** file.

You can run the project by executing the command `dotnet run` (or if you are using Visual Studio Community, set project **WebApiDemo** as the startup project and then start debugging it)

Then navigate to [https://localhost:7101/swagger/index.html](https://localhost:7101/swagger/index.html) which is the default Url of the Swagger UI.
You can test the predefined endpoint. Try it out, and you see that right now we don't need any authorization to fetch the data. This is very risky if we are doing some real-world projects when data is something very important and needs to be protected.

## Duende Identity Server Project

### Setup Identity Server Project
As ASP.NET suggested in their document. We will use ASP.NET Core Identity for authenticating and combine it with Duende Identity Server for API authorization: [https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity-api-authorization?view=aspnetcore-6.0](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity-api-authorization?view=aspnetcore-6.0)

Fortunately, Duende Identity Server comes up with a nearly perfect document and supporting templates. The [Quickstarts](https://docs.duendesoftware.com/identityserver/v6/quickstarts/1_client_credentials/) has a template that integrates with ASP.NET Core Identity already. We will use that template to roll out the new project

To use Duende IdentityServer templates, the first thing we have to do is add their templates to our **dotnet cli** template by using the command:
```bash
dotnet new --install Duende.IdentityServer.Templates
```

Now if you run the command `dotnet new -l` to list out all the installed templates. you would notice the Duende IdentityServer templates

![Duende Identity Server Template CLI](https://res.cloudinary.com/baonguyen-dev/image/upload/v1648547575/Next.js%20SPA%2C%20Identity%20Server/identity_server_templates_cli_m2cafw.png)

We are going to use the **Duende IdentityServer with ASP.NET Core Identity (isaspid)** template.
Back to our bash shell, still inside the solution folder
```bash
# create the IdentityServer Project named IdentityServerAspNetIdentity
dotnet new isaspid -n IdentityServerAspNetIdentity

# Add the project to our soution to make it appear in Visual Studio Community Explorer
dotnet sln add IdentityServerAspNetIdentity\IdentityServerAspNetIdentity.csproj
```

The cli will prompt to ask if you want to seed the data. choose “Y” for “yes” if you want to use the default SQLite database. Otherwise, you want to use a different database, simply choose “N” for “no”, update the ConnectionStrings inside **applicationsettings.json** file, then run the command `dotnet run /seed` to seed the data again.

The seed action will populate the user database with “alice” and “bob” users. The default passwords are “Pass123$”.

Run the project by executing the command `cd IdentityServerAspNetIdentity && dotnet run`. The project will run on port 5001 and you can login using *alice/Pass123$* or *bob/Pass123$*.

Try to login and now we have an authenticated session on our IdentityServer, we need to map this session to authorize our API.

Right now we can define the API Scope for our WebApiDemo. This template uses a "code as configuration" approach, i.e. we will config our API directly within the code.
Open the **Config.cs** file, there are already 3 pre-defined collections: IdentityResources, ApiScopes, and Clients. We are going to modify the ApiScopes and the Clients fields.

Define our scope 
```csharp
// Config.cs
public static IEnumerable<ApiScope> ApiScopes =>
        new ApiScope[]
        {
            new ApiScope("SampleAPI"),
        };
```

### Using Swagger UI to authorize

Back to our WebApiDemo project, we will add the configuration to protect our API first

We will use JWT Bearer scheme to protect our API, and this bearer authentication token is used by the IdentityServer project.
We will add authentication middleware to the pipeline from **Microsoft.AspNetCore.Authentication.JwtBearer** nuget package.

Run this command in the WebApi folder
```bash
# Add JWT Bearer Authentication. We want to authenticate users of our API using tokens issues by the IdentityServer
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
```
Then add JWT Bearer authentication services to ASP.NET services collection and configure **Bearer** as the default Authentication Scheme.
Also we need to configure the Authorization Service to accept only request from the clients which have the API Scope.
```csharp
// Configure Bearer as the default Authentication Scheme
// Add Jwt Bearer authentication services to the service collection to allow for dependency injection
builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer("Bearer", options => {
				options.Authority = "https://localhost:5001";
// Our API app will call to the IdentityServer to get the authority

        options.TokenValidationParameters = new TokenValidationParameters()
        {
            ValidateAudience = false, // Validate 
        };
		});

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("ApiScope", policy =>
    {
        policy.RequireAuthenticatedUser();
        policy.RequireClaim("scope", "SampleAPI");
    });
});

```
**SampleAPI** is the Scope Id we use to call our API resource


Config the endpoints to require the Authorization which has the "ApiScope" policy
```csharp
// Program.cs
// Map API Endpoint with the Authorization Policy
app.MapControllers().RequireAuthorization("ApiScope");
```

Add the **[Authorize]** attribute to **WeatherForcastController** to apply the authorization
```csharp
[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
// ...
}
```
Run the project and test the endpoint, you will see that this time the request is failed, and the server response with status 401 indicates that the request is not authorized

#### Define the client for Swagger UI
Back to the **Config.cs** file. We are going to modify the **Client** fields
```csharp
public static IEnumerable<Client> Clients =>
        new List<Client>
            {
                // Swagger client
                new Client
                {
                    ClientId = "api_swagger",
                    ClientName = "Swagger UI for Sample API",
                    ClientSecrets = {new Secret("secret".Sha256())}, // change me!

                    AllowedGrantTypes = GrantTypes.Code,

                    RedirectUris = {"https://localhost:7101/swagger/oauth2-redirect.html"},
                    AllowedCorsOrigins = {"https://localhost:7101"},
                    AllowedScopes = new List<string>
                    {
                        "SampleAPI"
                    }
                },
            };
```
#### Adding OAuth Support to Swagger UI.
The **WebApiDemo** project is using the **Swashbuckle** nuget package to build the API documentation.
We're going to modify the **SwaggerGen** service.

We have to describe how our API is secured by defining one or more security schemes. We are securing our API using the OAuth2 scheme. Since our Swagger UI is going to run in the end-user’s browser, and access tokens are going to be required by JavaScript running in that browser. So we will define an OAuth2 Authorization Code Flow

We need to tell **Swashbuckle** the location of our authorization Url, token endpoint Url and what scopes it is going to use. You can get these 2 links through the IdentityServer disco document (start your IdentityServer, locate to the Url [https://localhost:5001/.well-known/openid-configuration](https://localhost:5001/.well-known/openid-configuration). You may need a JSON reader to read the JSON data, I’m using the JSON formatter Chrome extension
![Local Identity Server disco doc](https://res.cloudinary.com/baonguyen-dev/image/upload/v1648547579/Next.js%20SPA%2C%20Identity%20Server/identity_server_disco_doc_mlilov.png)

```csharp
// Program.cs
builder.Services.AddSwaggerGen(options =>
{
    options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
    {
        Type = SecuritySchemeType.OAuth2,
        Flows = new OpenApiOAuthFlows
        {
            AuthorizationCode = new OpenApiOAuthFlow
            {
                AuthorizationUrl = new Uri("https://localhost:5001/connect/authorize"),
                TokenUrl = new Uri("https://localhost:5001/connect/token"),
                Scopes = new Dictionary<string, string>
                            {
                                {"SampleAPI", "API - full access"}
                            }
            },
        }
    });
});
```
Next, we need to indicate which operations the scheme is applicable to. You can apply the scheme globally using the `AddSecurityRequirement` method.

In the end, our Swagger Service definition would look like

```csharp
// Program.cs
builder.Services.AddSwaggerGen(options =>
{
    // Scheme Definition 
		options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
    {
        Type = SecuritySchemeType.OAuth2,
        Flows = new OpenApiOAuthFlows
        {
            AuthorizationCode = new OpenApiOAuthFlow
            {
                AuthorizationUrl = new Uri("https://localhost:5001/connect/authorize"),
                TokenUrl = new Uri("https://localhost:5001/connect/token"),
                Scopes = new Dictionary<string, string>
                            {
                                {"SampleAPI", "Sample API - full access"}
                            }
            },
        }
    });

		// Apply Scheme globally
		options.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
            },
            new[] { "SampleAPI" }
        }
    });
});
```
Then we need to tell our SwaggerUI to authorize using [PKCE](https://oauth.net/2/pkce/)

```csharp
app.UseSwagger();
    app.UseSwaggerUI(options =>
    {
        options.OAuthUsePkce();
    });
```
And that’s it. now you start authorizing using Swagger UI and call the Sample API endpoint’s directly inside your development environment.

Start the two projects and test the functions. you will notice we have a new Authorize button inside Swagger UI.
![Swagger UI Authorize popup](https://res.cloudinary.com/baonguyen-dev/image/upload/v1648547576/Next.js%20SPA%2C%20Identity%20Server/swagger_authorization_popup_gwk4uw.png)

Input the **client_id** and **client_secret**, which we’ve specified inside the IdentityServer config file (should be *api_swagger/secret*. Also, remember to tick on the **SampleAPI** scope.

You will be navigated to the Identity Server MVC UI for login, input **alice/Pass123$** to login then the Application will redirect you back to the Swagger application.

Try it out on the endpoint then you should be able to get the response data.


## Setup our main application - Next.js SPA Project
If you get through the process of authenticating SwaggerUI successfully, then implementing authentication for Next.js will not trouble you. They’re the same 2 steps, configure the client inside IdentityServer then add the Next.js Client to connect with it.

### Adding Next.js Client configuration in IdentityServer 

```csharp
// Config.cs
public static IEnumerable<Client> Clients =>
        new List<Client>
            {
				// SwaggerUI Client 
				// ...

                // NextJs client
                new Client
                {
                    ClientId = "nextjs_web_app",
                    ClientName = "NextJs Web App",
                    ClientSecrets = {new Secret("secret".Sha256())}, // change me!
                    AllowedGrantTypes =  new[] { GrantType.AuthorizationCode },
                    
                    // where to redirect to after login
                    RedirectUris = { "http://localhost:3000/api/auth/callback/sample-identity-server" },
                    // where to redirect to after logout
                    PostLogoutRedirectUris = { "http://localhost:3000" },
                    AllowedCorsOrigins= { "http://localhost:3000" },

                    AllowedScopes = new List<string>
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        "SampleAPI"
                    },
                }
            };
```

- Our Next.js is going to run on port 3000, so I set the CORS origin to allow the request from **http://localhost:3000**. `PostLogoutRedirectUris` is an optional
- `RedirectUris` is a list of allowing redirect Uri, the current value is the default url we will config using NextAuth.js


### Create the Next.js SPA client
Next.js community has an awesome open source project helping us authenticate called NextAuth.js.

It has many built-in support for popular services including IdentityServer4 (Predecessor of Duende IdentityServer, and we’re going to use that provider to connect with our IdentityServer)

We’ll start with its [Getting Started Typescript Example](https://next-auth.js.org/getting-started/typescript)

Create a folder name **apps** inside our solution folder and then place the example into it. Open the folder using your favorite Text IDE

Run the command `npm install` to install the dependencies.

Copy the **.env.local.example** file in the directory to **.env**. Next.js will get the environment variables from this file when starting the application.

Inside the **.env** file, at the end, add this line `NODE_TLS_REJECT_UNAUTHORIZED=0` to make Next.js bypass SSL checking in localhost.

This template already added a **[...nextauth].ts** file for you inside **pages/api/auth** folder. There’re several default providers, feel free to remove them.

We are going to add our IdentityServer4 Provider, take a look at the [NextAuth.js documentation](https://next-auth.js.org/providers/identity-server4) about how to config it.

Simply, we just have to add this into the provider array: 
```tsx
import IdentityServer4Provider from 'next-auth/providers/identity-server4'
// ...
providers: [
  IdentityServer4Provider({
      id: "demo-identity-server",
      name: "Demo IdentityServer",
      authorization: { params: { scope: "openid profile SampleAPI" } },
      issuer:  "https://localhost:5001/",
      clientId: "nextjs_web_app",
      clientSecret: "secret",
  })
}
// ...

```
And it’s all set. now start the Identity Server and the Next.js applications to check what we’ve done so far

![Next.js successfully authorization](https://res.cloudinary.com/baonguyen-dev/image/upload/v1648627638/Next.js%20SPA%2C%20Identity%20Server/succesful_x8mpmn.gif)

## Summary
That's it. We could connect call a protected endpoint from our Next.js SPA. But it's still not perfect. In the real world, there's sometimes you don't want to stay on the same page when doing the authorization. And that is Flow 3 that we will cover in the next article.

Thanks for reading!

## References
- [Duende Identity Server documentation](https://docs.duendesoftware.com/identityserver/v6)
- [Swashbuckle Github repo](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)
- [Scott Brady awesome article](https://www.scottbrady91.com/identity-server/aspnet-core-swagger-ui-authorization-using-identityserver4)