# Getting Started

## Introduction

Building authentication from scratch is a trap. We've seen teams spend a lot of time implementing OAuth 2.0 and OpenID Connect flows, only to discover edge cases that compromise security. Token validation, session management, PKCE flows. Each piece seems simple until you're debugging why production users can't log in.

At Abblix, we develop Oidc.Server: our certified OpenID Connect and OAuth 2.0 library for .NET. We've handled the complexity so you don't have to. This guide shows you how to build a working [OpenID Connect Provider](https://www.abblix.com/en/docs/glossary-overview#openid-connect-provider) in under an hour using [ASP.NET MVC](https://www.abblix.com/en/docs/glossary-overview#asp-net-mvc) and the Abblix OIDC Server solution, complete with login flows, token issuance, and proper session management.

By the end, you'll have two applications talking OIDC: a provider that authenticates users and a client that trusts it. You'll understand why the protocol works this way and where your customizations fit.

## TL;DR

You'll build a working OpenID Connect authentication flow using ASP.NET MVC. We create two applications: an authentication server that handles logins and issues tokens, and a web application that relies on that server to authenticate users. The authentication server uses Abblix OIDC Server, cookie authentication, and in-memory storage. The web application uses Microsoft's OpenIdConnect middleware. By the end, you'll have a complete login flow: users authenticate, view their claims, and log out properly.

### What You'll Build

OpenIDProviderApp - Your authentication server:
- Handles user logins with email/password
- Issues access tokens and ID tokens
- Manages user sessions with cookies
- Implements OpenID Connect endpoints (`/connect/authorize`, `/connect/token`, `/connect/endsession`)
- Uses Abblix OIDC Server for protocol handling

TestClientApp - Your protected application:
- Redirects users to OpenIDProviderApp for authentication
- Receives tokens after successful login
- Displays user claims from the [ID token](https://www.abblix.com/en/docs/glossary-overview#id-token-identity-token)
- Handles logout across both applications
- Uses Microsoft's OpenIdConnect middleware

We'll use real credentials, actual HTTP redirects, and patterns you can extend straight into a production app. This isn't a toy example. It's the foundation you'd carry forward.

Here's the path we'll take:

- Set up both projects: two ASP.NET MVC applications with the right ports
- Configure OpenIDProviderApp: add Abblix OIDC Server, create a login page, handle user authentication
- Configure TestClientApp: add OpenIdConnect middleware, display claims, implement logout
- Test the complete flow: login, view claims, logout, everything working together

By the end of this guide, you'll fully understand [OpenID Connect](https://www.abblix.com/en/docs/glossary-overview#openid-connect) and have a functioning implementation using ASP.NET MVC and Abblix OIDC Server.

## Create New ASP.NET MVC Projects

Here's how to create two new ASP.NET MVC projects and add them to a solution using the .NET CLI (Command Line Interface):

- **Open** your command prompt or terminal.

- **Create** a new solution:
   ```bash
   dotnet new sln -n GettingStarted
   ```

- **Create** the first project, `OpenIDProviderApp`:
   ```bash
   dotnet new mvc -n OpenIDProviderApp
   ```

- **Create** the second project, `TestClientApp`:
   ```bash
   dotnet new mvc -n TestClientApp
   ```

- **Add** both projects to the solution:
   ```bash
   dotnet sln add ./OpenIDProviderApp/OpenIDProviderApp.csproj
   dotnet sln add ./TestClientApp/TestClientApp.csproj
   ```

These commands will set up two ASP.NET MVC projects and organize them within a single solution.

### Change Default Port Numbers

Setting specific port numbers for your applications ensures predictable and stable interaction between them. This is particularly important when your applications need to communicate securely, such as in the case of an OpenID Connect provider and a client application. Here's how you can set specific port numbers using the .NET CLI and editing configuration files directly, instead of using Visual Studio UI.

Fixed port numbers remove uncertainty around application URLs, making it easier for developers to remember and use them. Designating specific ports avoids conflicts with other applications on the same machine and simplifies the configuration of both applications, especially for network-related settings like callback URLs and [CORS](https://www.abblix.com/en/docs/glossary-overview#cors) policies. This also prepares your development environment for production scenarios where specific ports might be required.

### Steps to Set Port Numbers

Modify the `launchSettings.json` files for each project. This file is typically found in the `Properties` folder of each ASP.NET Core project. If it doesn't exist, you might need to create it.

- For OpenIDProviderApp:
    ```json
    {
        "profiles": {
            "https": {
                "commandName": "Project",
                "dotnetRunMessages": true,
                "launchBrowser": true,
                "applicationUrl": "https://localhost:5001",
                "environmentVariables": {
                "ASPNETCORE_ENVIRONMENT": "Development"
                }
            }
        }
    }
    ```

- For TestClientApp:
    ```json
    {
        "profiles": {
            "https": {
                "commandName": "Project",
                "dotnetRunMessages": true,
                "launchBrowser": true,
                "applicationUrl": "https://localhost:5002",
                "environmentVariables": {
                "ASPNETCORE_ENVIRONMENT": "Development"
                }
            }
        }
    }
    ```

### Checking Port Availability

Before running your applications, check if the designated ports (5001 for `OpenIDProviderApp` and 5002 for `TestClientApp`) are available. On Windows, use `netstat -an | find "5001"` and `netstat -an | find "5002"` to see if those ports are already in use. On Linux or Mac, use `sudo lsof -i :5001` and `sudo lsof -i :5002` to check for port usage. These commands help verify that the specified ports are not occupied, ensuring that your applications run without interference.

## Configuring OpenIDProviderApp

The OpenIDProviderApp implements the core functions of an OpenID Connect Provider: it verifies user identities and issues tokens as required by the protocol.
By setting up these features correctly, our application will be a trusted source for other applications that need user authentication and authorization services.

### Add Abblix.Oidc.Server.Mvc NuGet Package

To add the necessary [NuGet Package](https://www.abblix.com/en/docs/glossary-overview#nuget-package) using the command line, follow these steps:

- Open a terminal.
- Navigate to your project directory. Use the `cd` command to change to the directory where your project is located.
- Run the following command to install the Abblix.Oidc.Server.Mvc package:
   ```bash
   dotnet add package Abblix.Oidc.Server.Mvc
   ```

This command will download and install the latest available version of the Abblix.Oidc.Server.Mvc NuGet package into your project, making it ready for further configuration.

### Register Abblix Services into Dependency Injection

**File: Program.cs**

Here's how to integrate Abblix services into your ASP.NET Core application.

- At the beginning of the file, include `using` directives for the necessary namespaces:
  ```csharp
  using Abblix.DependencyInjection;
  using Abblix.Jwt;
  using Abblix.Oidc.Server.Features.UserInfo;
  using Abblix.Oidc.Server.Mvc;
  using OpenIDProviderApp;
  ```
  These directives make the extension methods and types from the Abblix OIDC Server accessible within your application.

- After creating the `builder` instance but before building the app, add the registration for the Abblix services. Configure the options as needed for your application environment.

Here is how you can insert the necessary configurations:

```csharp
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();

// Register and configure Abblix OIDC Server
builder.Services.AddOidcServices(options =>
{
    // Client registrations are loaded from the Oidc section of appsettings.json
    builder.Configuration.Bind("Oidc", options);

    options.LoginUri = new Uri("/Auth/Login", UriKind.Relative);
    options.SigningKeys = [JsonWebKeyFactory.CreateRsa(PublicKeyUsages.Signature)];
});

var app = builder.Build();
```

The client registrations themselves live in configuration. Add the `Oidc` section to the `appsettings.json` file:

```js
{
  "Oidc": {
    "Clients": [
      {
        "ClientId": "test_client",
        // SHA-512 hash of the test secret "secret" - the provider stores hashes, never plain secrets
        "ClientSecrets": [ { "Sha512Hash": "vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==" } ],
        "TokenEndpointAuthMethod": "client_secret_post",
        "AllowedGrantTypes": [ "authorization_code" ],
        "PkceRequired": true,
        "RedirectUris": [ "https://localhost:5002/signin-oidc" ],
        "PostLogoutRedirectUris": [ "https://localhost:5002/signout-callback-oidc" ]
      }
    ]
  }
}
```

:::note[NOTE]
This setup provides a starting point for using the Abblix OIDC Server in your application. The `options` configured in the `AddOidcServices` method are important for setting up the OIDC server's behavior to meet your specific needs.
For example, the code snippet sets up a client with a hashed secret and specifies the grant types and client type. Later we will explore more detailed and customized configuration options,
focusing on adapting the OpenID server to different operational requirements.

The basic configuration above enables your application to interact with the client application requiring authentication and authorization services through the OpenID Connect protocol.
:::

#### Understanding `AddOidcServices` Configuration

**Configuration Method:**

```csharp
builder.Services.AddOidcServices(options => { ... });
```
- This line adds and configures the services necessary for the Abblix OIDC Server to operate within your application.
- The `AddOidcServices` extension method takes a lambda expression where you can specify various options to tailor the OIDC server's behavior.

**Configuring Clients:**

```js
"Oidc": {
  "Clients": [
    { "ClientId": "test_client", ... }
  ]
}
```
- This section defines a collection of client registrations, each representing a client application that will interact with your OIDC server. The `builder.Configuration.Bind("Oidc", options)` call in `Program.cs` loads them into the server options, so clients can be added or changed without recompiling.
- Each client is identified by a unique ID (`"test_client"` in this example), which is used by the server to recognize and handle requests from that client.

**Setting Client Secrets:**

A client secret acts as a password for the client application to authenticate itself to the authorization server.
When a client application requests access tokens or refresh tokens, it must prove its identity by presenting the client secret alongside the request.
This guarantees that only registered and verified clients can request tokens and access user information.

```js
"ClientSecrets": [ { "Sha512Hash": "vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==" } ],
```

The value is the Base64-encoded [SHA-512](https://www.abblix.com/en/docs/glossary-overview#sha-512) hash of the secret, so the configuration never contains the secret itself. You can produce it for your own secret with a one-liner:

```bash
echo -n 'secret' | openssl dgst -sha512 -binary | base64
```

:::warning[IMPORTANT]
**Security Notes for Production Environments:**
- Replace the placeholder "secret" with a strong and unique secret for each client application.
- Store client secrets securely by avoiding plain text; instead, store their hashes. Abblix configuration is designed to prevent storing raw passwords.
- Always ensure that secrets are stored securely using methods such as environment variables or dedicated secure vault solutions to manage sensitive information safely and reduce the risk of leakage.
:::

**Allowed Grant Types:**
```js
"AllowedGrantTypes": [ "authorization_code" ],
```

This code specifies the grant types the client can use. Here, the client is configured to use the [Authorization Code Flow](https://www.abblix.com/en/docs/glossary-overview#authorization-code-flow).

The Authorization Code flow is a secure [grant type](https://www.abblix.com/en/docs/glossary-overview#grant-type) ideal for clients that can maintain a client secret between themselves and the authorization server (typically server-side applications).
This flow is more secure than others, such as the [Implicit flow](https://www.abblix.com/en/docs/glossary-overview#implicit-flow), because the tokens are not exposed to the user or stored in potentially less secure places like the browser.
It also supports refresh tokens, which are essential for applications that require prolonged access to resources on the user's behalf without needing re-authentication.

How It Works:
- Initially, the user authenticates with the authorization server and grants the application permission to access their information.
- The authorization server does not directly issue tokens to the client. Instead, it issues an authorization code which is passed through the user's browser.
- The client application exchanges this authorization code for an [access token](https://www.abblix.com/en/docs/glossary-overview#access-token), and optionally, a [refresh token](https://www.abblix.com/en/docs/glossary-overview#refresh-token), using its client secret.
- The access token allows the application to request resources from the resource server and obtain information about the user.

**[Token Endpoint](https://www.abblix.com/en/docs/glossary-overview#token-endpoint) Auth and Proof Key for Code Exchange:**
```js
"TokenEndpointAuthMethod": "client_secret_post",
"PkceRequired": true,
```
- `TokenEndpointAuthMethod` determines how the client application authenticates itself at the token endpoint. Setting `client_secret_post` specifies that the client application includes the `client_id` and `client_secret` in the body of a POST request to the token endpoint.
- `PkceRequired` specifies whether Proof Key for Code Exchange ([PKCE](https://www.abblix.com/en/docs/glossary-overview#pkce)) is required. PKCE enhances the security of the Authorization Code flow.

:::warning[WARNING]
Setting `PkceRequired` to `false` might be suitable for trusted or internal clients but is strongly recommended to be `true` for public clients.
:::

**Redirect URIs:**
```js
"RedirectUris": [ "https://localhost:5002/signin-oidc" ],
"PostLogoutRedirectUris": [ "https://localhost:5002/signout-callback-oidc" ],
```
- `RedirectUris` are the URLs to which the OIDC server can send responses (tokens or [authorization codes](https://www.abblix.com/en/docs/glossary-overview#authorization-codes)) after authenticating the user.
- `PostLogoutRedirectUris` define where the user is redirected after logging out. These must be pre-registered to prevent [redirection attacks](https://www.abblix.com/en/docs/glossary-overview#redirection-attacks).

By specifying which URLs are allowed to receive tokens and codes, you protect the application from redirection attacks.
In such attacks, an unauthorized party could redirect a user to a malicious site instead of the intended destination after authentication or logout.
Pre-registering URIs ensures that the OIDC server only sends sensitive information to trusted locations.

**Additional Configuration Options:** 

**LoginUri:**
```csharp
options.LoginUri = new Uri("/Auth/Login", UriKind.Relative);
```
- `LoginUri` specifies the URL to which users are redirected to log in. This URI is relative, meaning it is appended to the base URI of your server. It is used primarily in scenarios where the user needs to authenticate before proceeding.

**SigningKeys Configuration:**
```csharp
options.SigningKeys = [JsonWebKeyFactory.CreateRsa(PublicKeyUsages.Signature)];
```
- `SigningKeys` includes cryptographic keys used for signing the tokens issued by your OIDC server. Specifically, the `JsonWebKeyFactory.CreateRsa(PublicKeyUsages.Signature)` function generates a new RSA key pair that is intended for signing. This key pair consists of both public and private components:
  - **Public Key:** Used by clients to verify the authenticity of the signed token.
  - **Private Key:** Held securely by the server to sign the tokens.

Signing tokens with RSA keys lets clients verify both the issuer and the integrity of the token contents. This prevents token forgery and tampering during the authentication flow.

### Authentication with Cookies

**File: Program.cs**

After configuring the Abblix services, it's necessary to set up the application's authentication mechanism.
Add the following code to the registration of services:

```csharp
builder.Services
    .AddAuthentication()
    .AddCookie();
```

#### Explanation

**AddAuthentication Method:**

```csharp
.AddAuthentication()
```
- This call to `AddAuthentication()` initializes the authentication services in your application, marking the foundational step for configuring user identification.
  Without this setup, the application lacks the mechanism to manage user sign-ins or to maintain user session states effectively.

**AddCookie Method:**

```csharp
.AddCookie();
```
- Following `AddAuthentication()`, the `.AddCookie()` method specifies that your application will use cookie-based authentication for session management.
  This approach is widely adopted in web applications for its effectiveness in tracking authenticated user sessions.

Upon a user's login to your OpenID Connect provider, the system generates a session cookie and dispatches it to the user's browser.
This cookie accompanies every subsequent server request, enabling the server to recognize the user and persist their logged-in status across the application's OIDC flows.
This mechanism lets the [authorization endpoint](https://www.abblix.com/en/docs/glossary-overview#authorization-endpoint) proceed with request handling based on the user's authenticated state, without forcing a repeat login.

Cookie-based authentication is also the foundation for Single Sign-On ([SSO](https://www.abblix.com/en/docs/glossary-overview#sso)).
SSO permits a user to authenticate once and access multiple applications without the need for repeated sign-ins.
Through the OIDC server's recognition of the session cookie, SSO lets an authenticated user obtain access tokens for other applications without re-entering credentials.

### Managing State Across Requests

For an OpenID Provider to function effectively, it must retain certain information between requests.
This includes authorization codes following successful authentication, authorization requests for Pushed Authorization Requests ([PAR](https://www.abblix.com/en/docs/glossary-overview#par)), and [JWT](https://www.abblix.com/en/docs/glossary-overview#jwt) statuses to manage their revocation.
The storage for these items needs to be persistent (retaining data even after an application restart) and durable, and often distributed if using multiple hosts for load balancing and high availability - a common practice today.

#### Using IDistributedCache for Persistent Storage

Abblix OIDC Server relies on the `IDistributedCache` interface, a standard in Microsoft environments, to store these entities.
The `IDistributedCache` interface provides a framework for implementing distributed cache solutions. It helps you maintain state across different servers and instances in a scalable manner.
This setup ensures that even in environments with high traffic and multiple servers, your application can efficiently retrieve and store critical data needed for processing OpenID Connect requests.

#### Setting Up a Distributed Cache

To set up a distributed cache, you can use [Redis](https://www.abblix.com/en/docs/glossary-overview#redis), [NCache](https://www.abblix.com/en/docs/glossary-overview#ncache), SQL Server, Memcached, Couchbase, or any other backend that supports scalable, distributed caching.
These systems are well-suited for applications requiring high availability and swift data access across multiple servers. Most modern caching solutions offer NuGet packages that implement `IDistributedCache`, simplifying integration into your ASP.NET projects.

Redis offers the `Microsoft.Extensions.Caching.StackExchangeRedis` package with support for data persistence and complex data types. NCache provides `Alachisoft.NCache.OpenSource.SDK` with strong scalability features. For applications already using SQL Server, the `Microsoft.Extensions.Caching.SqlServer` package offers convenient integration with Microsoft technologies. Memcached can be integrated through `EnyimMemcachedCore` for simpler workloads, while Couchbase's `Couchbase.Extensions.Caching` package brings rich querying capabilities and flexibility.

Choose the distributed cache that best aligns with your application's needs and existing infrastructure.

### Using MemoryCache for simplicity

To keep our test sample simple, we'll use the built-in `MemoryCache` implementation of `IDistributedCache`.
This method enables quick setup and is suitable for development environments or scenarios where distributed architecture is not required and the service runs on a single host.

Add the following code to your project setup:

```csharp
builder.Services.AddDistributedMemoryCache();
```
:::warning[IMPORTANT]
**Important Note for Production**

It's important to understand that `MemoryCache`, while easy to set up, should not be used in real-world production systems.
The primary reason is that `MemoryCache` is not truly distributed and does not share data across multiple instances or servers.
This leads to inconsistencies and issues in environments where high availability and resilience are required.
:::

Upcoming articles will cover how to implement your own persistent storage and define custom policies for managing them, so you can tailor the storage layer to your application's needs.
For now, we focus on simplicity to help you get up and running quickly.


### Using Cross-Origin Resource Sharing (CORS)

CORS is a security feature implemented in web browsers that controls whether web pages can make requests to a different domain than the one that served the web page.

Here is the complete middleware pipeline for `OpenIDProviderApp`:

```csharp
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();
app.UseCors();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();
```

Notice that `app.UseAuthentication()` is not added here. For `OpenIDProviderApp`, authentication state is managed entirely through cookies that Abblix OIDC Server issues and validates internally. The standard ASP.NET Core authentication middleware is only needed in client applications (like `TestClientApp`) that must read the identity from those cookies.

- `app.UseCors()` enables Cross-Origin Resource Sharing (CORS), allowing the application to accept requests from different origins. This matters when `PkceRequired = true` because the authorization code flow with PKCE involves multiple cross-origin requests between the client and the OpenID provider.

#### Implementation and Testing

For initial development and testing, using an in-memory cache to store authorization codes, tokens, and consent decisions may be enough.
This approach allows for rapid development and easy testing without the complexity of integrating with external storage systems.

For production, switch to a persistent storage backend that meets your scalability and security requirements.
Whether it's a relational database, a NoSQL database, or a key-value store like Redis, the choice should be based on thorough analysis and consultation with your architectural team.

### Creating a Login Page and Integrating It into the Authorization Flow

When a user attempts to access a protected resource and is redirected to the OpenID Provider's authorization endpoint, it signifies the start of the authorization flow.
The initial task for the OpenID Connect provider is to determine if the user is already authenticated. If not, the server must pause the authorization flow temporarily to handle the incoming authorization request:

- The server temporarily stores the details of the authorization request. This is essential as it allows the user to be redirected to a login UI without losing the context of the original request.
- The server then presents the user with an authentication interface, typically a login page, where they can enter their credentials (username and password).

#### How Abblix OIDC Server uses the Pushed Authorization Request (PAR)

In general, the PAR mechanism allows an authorization request to be sent to the OpenID Connect provider in advance of the user interaction at the authorization endpoint.
The server securely stores this request and issues a unique ID for it. This ID, known as the `request_uri`, represents the stored request.

The Abblix OIDC Server employs the PAR internally to pause and recover the authentication process later:

- Storing Requests using PAR: Abblix OIDC Server uses the PAR storage to remember the initial authorization request and generates a unique identifier for it.
- Redirect with `request_uri`: In directing the user to the login UI, Abblix OIDC Server appends a `request_uri` parameter containing the unique ID of the stored request. This ensures that the user's entry point into the authentication UI is directly linked back to their original authorization request.
- Handling Successful Authentication: After the user enters their credentials and is authenticated, the login UI redirects the user back to the authorization endpoint with the original `request_uri` parameter. The server retrieves the stored request details using this ID and resumes the authorization flow from where it left off.

Abblix OIDC Server avoids providing the full URI for redirection back from the login page to mitigate risks associated with open redirect vulnerabilities.
Additionally, using a `request_uri` value instead of a full redirection URI simplifies the URLs involved.

### Implementing the Login UI

Building a responsive, secure login UI matters because it is the user's first interaction with your authentication flow.
While MVC is used here for simplicity, in real-world scenarios, especially for applications requiring dynamic and responsive UIs, consider using modern SPA frameworks like React or Vue.
They offer more flexibility, better state management, and enhanced user experience compared to traditional MVC applications for complex interactive web applications.

To implement the login interface using an [MVC Controller](https://www.abblix.com/en/docs/glossary-overview#mvc-controller) and View in the `OpenIDProviderApp`, create a new controller named `AuthController.cs` under the `Controllers` directory to manage the authentication processes, including displaying and processing the login form. Create a view named `Login.cshtml` under the `Views/Auth` directory containing the HTML form where users input their credentials. Upon form submission, the `AuthController` validates the credentials and performs the redirect with the `request_uri`, as specified by the PAR mechanism to resume the OpenID Connect flow.

#### Create an Authentication Controller

**File: Controllers/AuthController.cs**

First, let's set up a controller that manages authentication requests. Navigate to the `Controllers` folder in your `OpenIDProviderApp` project and introduce a new Controller class named `AuthController`.

Add the following `using` directives at the top of the file. Two of them are type aliases: they resolve naming conflicts between Abblix and ASP.NET Core types with the same name:

```csharp
using Abblix.Oidc.Server.Features.RandomGenerators;
using Abblix.Oidc.Server.Features.UserAuthentication;
using Abblix.Oidc.Server.Model;
using Abblix.Oidc.Server.Mvc;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Mvc;
using Path = Abblix.Oidc.Server.Mvc.Path;
using UriBuilder = Abblix.Utils.UriBuilder;

namespace OpenIDProviderApp.Controllers;

public class AuthController : Controller
{
}
```

#### Create the Login Action

The `Login` action in the `AuthController` manages the first step in the authentication process by presenting the login form to the user.
This method also uses a parameter named `request_uri` which is essential to resume the OpenID Connect flow later.

Add the code of the action as shown below:

```csharp
// GET: Auth/Login
public IActionResult Login([FromQuery(Name = "request_uri")] string requestUri)
{
	// Return a view with login/password inputs and sign-in button
    return View(new { requestUri });
}
```

The `request_uri` parameter holds the identifier for the initial authorization request stored in the PAR storage.
This identifier lets the OpenID Connect provider retrieve and continue the original authorization request after the user successfully logs in.
When a user needs to access a resource that requires authentication, the server securely stores the authorization request and generates a unique identifier (`request_uri`) for this stored request.
The user is then redirected to the login page with this `request_uri` included as a query parameter.

The action method captures this `request_uri` from the query parameters when the user is redirected to the login page.
It then passes this value to the View to maintain state between the login form and the authorization process.
The login view receives the `request_uri` as part of its model data, which it includes in a hidden form field when submitting the login credentials.
This preserves the `request_uri` throughout the user session and post-authentication process, so the server can fetch and continue the original request without losing context.

### Create the Login View

**File**: `Views/Auth/Login.cshtml`

First, create the directory structure for your authentication views. In the `Views` folder of your `OpenIDProviderApp` project, create a new folder named `Auth` - this corresponds to the `AuthController` we created earlier.

Inside the `Views/Auth` directory, create a new file named `Login.cshtml`. This login form view will handle user credential submission.

**Structure the Login Form**:
Use HTML to construct a form that includes input fields for email and password, as well as a submit button. This form will handle user inputs and submit them to your server for authentication.

```html
<!-- Login form designed for user authentication -->
<form asp-action="Login" method="post">
    <div>
        <label for="email">Email</label>
        <input type="email" id="email" name="email" required />
    </div>
    <div>
        <label for="password">Password</label>
        <input type="password" id="password" name="password" required />
    </div>
    <div>
        <!-- Display validation errors here -->
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
    </div>
    <div>
        <!-- Hidden field to maintain request_uri during the login process -->
        <input type="hidden" id="requestUri" name="requestUri" value="@Model.requestUri"/>
        <button type="submit">Login</button>
    </div>
</form>
```

**Explanation**:
- The form uses the `asp-action="Login"` tag helper to specify which action method on the server it should call when submitted. This ensures the form data is sent to the `Login` POST method in `AuthController`.
- Input fields for `email` and `password` are marked with `required`, making sure users cannot submit the form without filling out these fields.
- The `@Html.ValidationSummary` helper method is used to display any validation errors that occur during the login process.
- A hidden input field named `requestUri` maintains the continuity of the login process by preserving the `request_uri` value across the form submission. This matters for the OpenID Connect flow: after successful authentication, the user can be redirected back to the originally requested resource or action.

### Implement a test storage for user accounts

**File: TestUserStorage.cs**

The Abblix OIDC Server provides an interface named `IUserInfoProvider`, which serves as a contract between Abblix OIDC Server's core functionalities and your application's user data storage.
This interface mandates the implementation of a method, `GetUserInfoAsync`, which asynchronously fetches a user's claims based on a specified [subject identifier](https://www.abblix.com/en/docs/glossary-overview#subject-identifier).
These claims can include simple and structured values as requested by the client application.

Create a class that implements the `IUserInfoProvider` interface in your `OpenIDProviderApp` project. This class will be responsible for retrieving user information based on the subject identifier and the requested claims from the internal list of users:

```csharp
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Nodes;
using Abblix.Jwt;
using Abblix.Oidc.Server.Features.UserAuthentication;
using Abblix.Oidc.Server.Features.UserInfo;

namespace OpenIDProviderApp;

/// <summary>
/// Represents user information, including subject identifier and profile attributes like name and email.
/// </summary>
public record UserInfo(string Subject, string Name, string Email, string Password);

/// <summary>
/// Provides a test storage implementation for user information, simulating a database of users.
/// </summary>
public class TestUserStorage(params UserInfo[] users) : IUserInfoProvider
{
    /// <summary>
    /// Asynchronously retrieves user information based on an authentication session and a collection of requested claims.
    /// </summary>
    public Task<JsonObject?> GetUserInfoAsync(AuthSession authSession, IEnumerable<string> requestedClaims)
    {
        var userInfo = GetUserInfo(authSession.Subject, requestedClaims);
        return Task.FromResult(userInfo);
    }

    /// <summary>
    /// Retrieves user information for a specific subject with respect to requested claims.
    /// </summary>
    /// <param name="subject">The subject identifier for which user information is requested.</param>
    /// <param name="requestedClaims">The claims that determine which information to include in the response.</param>
    /// <returns>A <see cref="JsonObject"/> containing the requested user information, or null if no user matches the subject.</returns>
    private JsonObject? GetUserInfo(string subject, IEnumerable<string> requestedClaims)
    {
        var user = users.FirstOrDefault(user => user.Subject == subject);

        if (user == null)
        {
            return null;
        }

        var result = new JsonObject();
        foreach (var claim in requestedClaims)
        {
            switch (claim)
            {
                case IanaClaimTypes.Sub:
                    result.Add(claim, user.Subject);
                    break;
                case IanaClaimTypes.Email:
                    result.Add(claim, user.Email);
                    break;
                case IanaClaimTypes.Name:
                    result.Add(claim, user.Name);
                    break;
            }
        }
        return result;
    }

    /// <summary>
    /// Attempts to authenticate a user based on their email and password.
    /// </summary>
    /// <param name="email">The email of the user attempting to authenticate.</param>
    /// <param name="password">The password provided for authentication.</param>
    /// <param name="subject">When this method returns, contains the subject identifier of the authenticated user if the return value is true; otherwise, null.</param>
    /// <returns>true if the authentication is successful; otherwise, false.</returns>
   public bool TryAuthenticate(
        string email,
        string password,
        [NotNullWhen(true)] out string? subject)
    {
        foreach (var user in users)
        {
            if (user.Email == email && user.Password == password)
            {
                subject = user.Subject;
                return true;
            }
        }

        subject = null;
        return false;
    }
}
```

This simplified implementation uses in-memory user accounts for demonstration purposes: a deliberate choice to keep the example focused.
In a real application, you would modify this code to connect to a database or another service that dynamically retrieves user accounts.

:::note[NOTE]
**Security Note on Storing Passwords**

It is important to understand that in real-world production environments, storing raw passwords is considered a bad practice due to the high risk of security breaches.
For our test sample, we use hard-coded user credentials only for simplicity.
However, in real scenarios, if a user database is compromised, raw passwords could be accessed directly by unauthorized parties, leading to severe security issues and potential data breaches.
:::

#### Best Practices for Storing Passwords

Always store passwords as hashes, not plain text. Hashing converts the original password into a different string, and adding a salt (a random string) before hashing ensures that even identical passwords produce unique hash values. Use computationally demanding hash functions designed for secure password storage, such as [PBKDF2](https://www.abblix.com/en/docs/glossary-overview#pbkdf2), [Bcrypt](https://www.abblix.com/en/docs/glossary-overview#bcrypt), or [Argon2](https://www.abblix.com/en/docs/glossary-overview#argon2). These functions resist brute-force attacks and make it computationally infeasible to derive the original password from the hash.

As technology advances and computational power increases, periodically review and update your hashing strategies to safeguard against new threats. Implement policies that promote or enforce the use of strong, unique passwords among users to reduce the risk of attacks succeeding. Adhering to these practices significantly improves the security of your authentication systems and safeguards user data from potential threats.

#### Register the Implementation in the Dependency Injection

**File: Program.cs**

To integrate your `TestUserStorage` class as an implementation of `IUserInfoProvider` within your application, register it so it is correctly configured and accessible throughout your application, including the internal components of the Abblix OIDC framework.

```csharp
var builder = WebApplication.CreateBuilder(args);

// Add the TestUserStorage as a singleton service in the DI container.
var userInfoStorage = new TestUserStorage(
	new UserInfo(
		Subject: "1234567890",
		Name: "John Doe",
		Email: "john.doe@example.com",
		Password: "Jd!2024$3cur3")
);
builder.Services.AddSingleton(userInfoStorage);

// Use AddAlias to register TestUserStorage also as an implementation of IUserInfoProvider.
builder.Services.AddAlias<IUserInfoProvider, TestUserStorage>();
```

:::warning[IMPORTANT]
The `AddAlias` method is part of the Abblix.DependencyInjection package. To use this method, ensure you include the appropriate namespace in your file:
:::

```csharp
using Abblix.DependencyInjection;
```

This method registers `TestUserStorage` not only as a service in its own right but also as the implementation for `IUserInfoProvider`.
This setup ensures that a single instance of `TestUserStorage` exists in the application.
And even when the `IUserInfoProvider` is requested, the same instance of `TestUserStorage` is used.

In the code snippet above, there is one user account defined with the email `john.doe@example.com` and the password `Jd!2024$3cur3`. You can use it for initial testing.
Of course you can also expand this setup later to include more user accounts or integrate it with a database for a more dynamic approach.

### Handle Authentication in the AuthController

**File: Controllers/AuthController.cs**

To manage the login form submissions, add a new method named `Login` designed to handle POST requests in the `AuthController`. Since a GET method for login already exists, this new POST method will be clearly distinguished by using the [HttpPost] attribute. This attribute ensures that the method processes form submissions rather than initial page requests.

Here's the critical moment: a user just submitted credentials. Three things must happen in exact order. First, validate the credentials. If wrong, show an error and redisplay the login form. If credentials are valid, create an authenticated session by generating a session cookie so the user stays logged in across requests. Finally, resume the OIDC flow by redirecting back to `/connect/authorize` with the original `request_uri`, letting Abblix OIDC Server continue where it paused.

That third step is subtle but crucial. When the user first tried to access `TestClientApp`, they got redirected to `/connect/authorize`, which paused the flow and sent them to our login page with a `request_uri` parameter. That parameter is how we tell the authorization endpoint "continue that original request."

Without it, the authorization endpoint wouldn't know which client initiated the request or where to redirect after authentication.

```csharp
// Existing GET: Auth/Login method is already defined here

// POST: Auth/Login
[HttpPost]
public async Task<IActionResult> Login(
	[FromServices] IAuthSessionService authService,
	[FromServices] ISessionIdGenerator sessionIdGenerator,
	[FromServices] IUriResolver uriResolver,
	[FromServices] TestUserStorage userStorage,
	[FromForm] string email,
	[FromForm] string password,
	[FromForm] string requestUri)
{
	// Attempt to authenticate the user with provided credentials
	if (!userStorage.TryAuthenticate(email, password, out var subject))
	{
		// Return an error message to the view to inform the user
		ModelState.AddModelError("", "Invalid username or password");
		return View(new { requestUri });
	}

	// If authentication is successful, create a new authentication session
	var authSession = new AuthSession(
		subject,
		sessionIdGenerator.GenerateSessionId(),
		DateTimeOffset.UtcNow,
		CookieAuthenticationDefaults.AuthenticationScheme);

	// Sign in the user using the authentication service
	await authService.SignInAsync(authSession);

	// Redirect the user to the authorization endpoint URL, recovering the OIDC flow
	var authorizeUrl = new UriBuilder(uriResolver.Content(Path.Authorize))
		{ Query = { [AuthorizationRequest.Parameters.RequestUri] = requestUri } };
	return Redirect(authorizeUrl);
}
```

This process starts with authenticating the user using the `TryAuthenticate` function. If the credentials are incorrect, an error message is displayed on the login page.
If authentication succeeds, it triggers the creation of a new `AuthSession` and formal login through `authService.SignInAsync()`.

Finally, the user is redirected to the authorization endpoint. The redirect URL is constructed using two tools:

- `IUriResolver.Content()` - Converts route paths (like given `Path.Authorize`) into absolute URIs based on the current HTTP request context. It handles both application-relative paths (`~/connect/authorize`) and configurable route templates, ensuring URLs are properly formed with the correct scheme and host.

- `UriBuilder` - Provides a fluent API for constructing URIs with query parameters. Using the Query indexer (`{ Query = { [key] = value } }`), you can add parameters without manual string concatenation or encoding, making the code cleaner and less error-prone.

This resumes the OpenID Connect flow, leading to token issuance.

The method effectively demonstrates very basic credential verification.
In a production environment, it would need enhancement to ensure secure and efficient handling of user credentials according to best cybersecurity practices.

However, detailing the implementation of such a system is beyond the scope of this guide.
Our current focus is to provide a foundational understanding and implementation of authentication workflows using OpenID Connect in a controlled test environment.

## Configuring a Test Client Application

Ensuring your OpenID Connect server operates as expected is important.
This phase involves a thorough testing process to validate the entire authentication flow - from user authentication and authorization to token issuance
and access to protected resources. Having established `TestClientApp`, we now proceed to turn it into a fully-functional OpenID Connect client.

### Add Microsoft.AspNetCore.Authentication.OpenIdConnect NuGet Package

Add the necessary NuGet package from the command line.

Navigate to your project directory for `TestClientApp`. Run the following command to install the `Microsoft.AspNetCore.Authentication.OpenIdConnect` package:
```bash
dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect
```

This installs the Microsoft.AspNetCore.Authentication.OpenIdConnect NuGet package into your `TestClientApp` project, enabling it to use OpenID Connect for authentication.
This is essential for setting up the OpenID Connect client capabilities in your ASP.NET application.

### Establish the Authentication Schemes

TestClientApp combines two Authentication Schemes. Here's how they divide the work.

#### Understanding the Concept of Authentication Scheme

An Authentication Scheme in ASP.NET Core MVC is a named configuration that defines the mechanics of authentication for a specific context.
Each scheme is capable of performing a variety of operations, including:

- Challenge: Initiates authentication, typically by redirecting to a login page or challenging an API call.
- Sign-in: Manages the process of establishing an authenticated session after a user is authenticated.
- Sign-out: Handles the termination of an authenticated session, usually by clearing cookies or tokens.
- Authenticate: Responsible for validating authentication data (like cookies or tokens) in requests and assigning a user identity based on that data.

A scheme may implement these operations itself or delegate them to another scheme, creating a flexible architecture.
For instance, the cookie-based scheme handles sign-in and sign-out operations directly, maintaining the session state after initial authentication.
However, for initiating authentication - known as the **challenge** operation - it relies on the OpenID Connect scheme.
This delegation relies on OpenID Connect for secure initial authentication, while the cookie scheme efficiently manages session persistence.
In our example, we use this approach to get the best of each scheme.

#### Cookie Authentication

**Role**: Cookie Authentication acts as the primary authentication scheme in `TestClientApp`, essential for managing the user session after the user has been authenticated
by an external provider such as OpenID Connect.

**How It Works**: After successful authentication via OpenID Connect, `TestClientApp` issues a session cookie containing the user's encrypted identity information.
This cookie authenticates subsequent requests from the user's browser, maintaining the session without continuous re-authentication.

#### OpenID Connect Authentication

**Role**: This serves as the secondary or external authentication scheme in `TestClientApp`, designed specifically for authenticating users against the OpenID Connect provider (`OpenIDProviderApp`).

**How It Works**: When a user attempts to access a protected resource without an active session, this scheme redirects them to the `OpenIDProviderApp` for authentication.
After successful authentication, the user is redirected back with an authorization code that `TestClientApp` exchanges for an identity token and possibly an access token to establish and manage the user session with a cookie.

#### Inter-Scheme Delegation

In our setup, while the Cookie Authentication scheme manages the sign-in and sign-out processes, it delegates the challenge operation to the OpenID Connect Authentication scheme.
This inter-scheme delegation allows `TestClientApp` to use OpenID Connect for handling initial user authentication requests and redirects, relying on the external authentication mechanisms provided by `OpenIDProviderApp`.

#### Integrating the Authentication Schemes

**File: Program.cs**

Add both authentication schemes to your test client application and configure them to work together:

- Configure both authentication schemes from the configuration:

```csharp
var configuration = builder.Configuration;

builder.Services
    .AddAuthentication(options => configuration.Bind("Authentication", options))
    .AddCookie(options => configuration.Bind(CookieAuthenticationDefaults.AuthenticationScheme, options))
    .AddOpenIdConnect(options => configuration.Bind(OpenIdConnectDefaults.AuthenticationScheme, options));
```

This configuration establishes Cookie Authentication as the primary means of maintaining user sessions within `TestClientApp`, while OpenID Connect Authentication handles initial user authentication through `OpenIDProviderApp`.
The cooperation between these schemes combines cookie-based sessions with OpenID Connect's federated identity capabilities into one secure authentication flow.

#### Configure the Middleware Pipeline

After registering the authentication services, you need to add the authentication middleware to the HTTP request pipeline. Add the following code after building the app:

```csharp
var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();  // Enable authentication middleware
app.UseAuthorization();   // Enable authorization middleware

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();
```

The order of middleware is critical:
- `UseAuthentication()` must come after `UseRouting()` and before `UseAuthorization()`
- This ensures that user authentication is processed before authorization checks

### Configure the Authentication

It's time to properly set up your `TestClientApp` to authenticate using the OpenID Connect provider.

**File: appsettings.json**

Add the following configuration settings to `appsettings.json`:

```json
"Authentication": {
    "DefaultScheme": "Cookies",
    "DefaultChallengeScheme": "OpenIdConnect"
},
"OpenIdConnect": {
    "SignInScheme": "Cookies",
    "SignOutScheme": "Cookies",
    "SaveTokens": true,
    "Scope": ["openid", "profile", "email"],
    "MapInboundClaims": false,
    "ResponseType": "code",
    "ResponseMode": "query",
    "UsePkce": true,
    "GetClaimsFromUserInfoEndpoint": true
}
```

**File: appsettings.Development.json**

Add the environment-specific settings to `appsettings.Development.json`:

```json
"OpenIdConnect": {
    "Authority": "https://localhost:5001",
    "ClientId": "test_client",
    "ClientSecret": "secret"
}
```

Let's break down these settings.

#### Authentication Section

- `DefaultScheme`: Specifies the primary method of authentication. Set to `"Cookies"`, it indicates that the application uses cookie-based authentication by default to handle user sign-ins and maintain session state.
- `DefaultChallengeScheme`: This specifies the scheme used when the application needs to actively challenge a user for authentication. By setting this to `"OpenIdConnect"`, the application is directed to use OpenID Connect whenever it encounters a scenario requiring user authentication without a valid cookie.

#### OpenIdConnect Section

- `SignInScheme` & `SignOutScheme`: Both set to `"Cookies"`, these settings govern how the application handles user sign-ins and sign-outs, respectively, linking the OpenID Connect authentication process to cookie-based session management.
- `Authority`: Defines the URL of the OpenID Connect provider, in this case, `https://localhost:5001`. This is where the application sends authentication and token requests.
- `ClientId` & `ClientSecret`: The `ClientId` is a unique identifier for the application registered with the OpenID Connect provider, while the `ClientSecret` is a secret key used to authenticate the client with the provider, enhancing security. It's crucial to keep the client secret secure, especially in production environments.
- `SaveTokens`: This setting, when enabled, instructs the application to save the tokens obtained during the authentication process, which may include identity, access, and refresh tokens. These tokens are useful for making API requests on behalf of the user.
- `Scope`: This array specifies the permissions or scopes requested by the application, such as `"openid"`, `"profile"`, and `"email"`. These scopes determine the extent of access to the user's information allowed by the application.
- `MapInboundClaims`: Setting this to `false` avoids the automatic conversion of JWT claims into Microsoft's proprietary claim types, allowing the application to use the original claims issued by the OpenID Connect provider.
- `ResponseType`: Specifies the type of response the client expects from the OpenID Connect provider. Setting `"code"` indicates that the Authorization Code flow is used.
- `ResponseMode`:  Defines how the authorization response is returned to the client. `"query"` means that the authorization code will be included in the query string of the redirect URI.
- `UsePkce`:  Indicates whether Proof Key for Code Exchange (PKCE) should be used. Setting this to `true` enhances the security of the Authorization Code flow.
- `GetClaimsFromUserInfoEndpoint`:  Instructs the application to retrieve additional claims about the user from the [UserInfo endpoint](https://www.abblix.com/en/docs/glossary-overview#userinfo-endpoint), supplementing those provided in the ID token.

### Implementing an Index Page to Display User Claims

To effectively display user claims in your `TestClientApp` as a result of successful authentication, you will need to configure the appropriate controller action and set up a view.

#### Secure the Index Page with User Authentication

**File**: `Controllers/HomeController.cs`

Ensure that only authenticated users can access the `Index` page by applying the `[Authorize]` attribute to the `Index` action method in the `HomeController`.

**Update the Method as Follows**:

```csharp
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

public class HomeController : Controller
{
    [Authorize]
    public IActionResult Index()
    {
        // Retrieve and pass the user's claims to the view
        return View(User.Claims);
    }
}
```

The `[Authorize]` attribute on the `Index` action ensures that only authenticated users can view the claims page. If a user attempts to access it without being authenticated, the system automatically issues a challenge, redirecting them to the login page.

#### Update the Index View to Display User Claims

**File**: `Views/Home/Index.cshtml`

Modify the `Index` view located in the `Views/Home` directory to dynamically display user claims, providing a straightforward visualization of the authenticated user's data:

```html
@model IEnumerable<System.Security.Claims.Claim>
@{
    ViewData["Title"] = "User Claims";
}

<h2>User Claims</h2>

@if (Model.Any())
{
    <ul>
        @foreach (var claim in Model)
        {
            <li>@claim.Type: @claim.Value</li>
        }
    </ul>
}
else
{
    <p>No claims available. Are you authenticated?</p>
}
```

This view is designed to receive an `IEnumerable<Claim>` as its model, which it uses to display each claim in a list format. It first checks if any claims are present. If claims exist, it lists each one, showing the type and value of the claim. If no claims are found, it displays a message querying if the user is authenticated. This serves as a direct indication that no user data has been retrieved, either due to lack of authentication or an issue in the claim retrieval process.

#### Implementing Logout Functionality

Now that login is working, let's implement logout. For proper logout in an OpenID Connect flow, you need to sign out from both the local application session and the [identity provider](https://www.abblix.com/en/docs/glossary-overview#identity-provider) session.

**Add the EndSession action to HomeController**

**File**: `Controllers/HomeController.cs`

Add the following using statements at the top of the file:

```csharp
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
```

Then add the `EndSession` action to your `HomeController`:

```csharp
public IActionResult EndSession()
{
    return SignOut(
        CookieAuthenticationDefaults.AuthenticationScheme,
        OpenIdConnectDefaults.AuthenticationScheme);
}
```

The `EndSession` action intentionally does NOT have the `[Authorize]` attribute. This is important because logout should work even if the user is already logged out or if the session has expired. Requiring authentication for logout would create a confusing loop where calling `/Home/EndSession` would redirect to login, then immediately log out after successful authentication.

**Why specify both schemes?**

You must explicitly sign out from both authentication schemes. This is the standard ASP.NET Core pattern - there is no configuration option to automatically link multiple schemes for sign-out. ASP.NET Core authentication handlers are designed to be independent and don't automatically chain sign-out operations.

By explicitly specifying both schemes in the `SignOut()` call, you ensure:

**Cookie Authentication Scheme:**
- Clears the local session cookie from `TestClientApp`
- Removes the user's authenticated state in the client application

**OpenID Connect Scheme:**
- Triggers the OIDC logout flow with `OpenIDProviderApp`
- Redirects the user to `/connect/endsession` on the provider
- Ensures the user is logged out from the identity provider itself
- Handles the redirect back to `PostLogoutRedirectUri` after logout completes

This provides complete session termination across both the client application and the identity provider.

**Add a logout link to the navigation**

**File**: `Views/Shared/_Layout.cshtml`

To allow users to easily access the logout functionality, add a logout link to the navigation bar. Open the `_Layout.cshtml` file in the `Views/Shared` directory and locate the navigation menu section. Add a second navigation list with the logout link aligned to the right:

```html
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
    <ul class="navbar-nav flex-grow-1">
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
        </li>
    </ul>
    <ul class="navbar-nav">
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="EndSession">Logout</a>
        </li>
    </ul>
</div>
```

The logout link calls the `EndSession` action, which handles the complete logout process across both the client application and the identity provider.

With the controller action, the claims view, and logout handling in place, `TestClientApp` displays the authenticated user's claims and signs the user out across both the application and the provider.

## Testing Your Applications

To test the setup of your OpenID Connect provider and client applications, run both applications simultaneously.

### Running the Applications
- Open two separate command prompts or terminal instances for `OpenIDProviderApp` and `TestClientApp`.
- In each terminal, navigate to the respective project directory of each application.
- Run the following command in both terminals: 
  ```bash
  dotnet run -lp https
  ```
  This command starts each application with HTTPS enabled, which is necessary for OpenID Connect operations.

### Testing the Authentication Flow

**Step 1: Access the client application**
- Open a web browser in incognito mode and navigate to `https://localhost:5002`
- **Expected:** Browser redirects you to `https://localhost:5001/Auth/Login?request_uri=...`
- **You should see:** The login page with email and password fields

**Step 2: Log in with test credentials**
- Enter email: `john.doe@example.com`
- Enter password: `Jd!2024$3cur3`
- Click the login button
- **Expected:** Brief redirect through authorization endpoint, then back to TestClientApp
- **You should see:** The URL changes to `https://localhost:5002/` and displays the claims page

**Step 3: Verify authentication succeeded**
- **You should see:** A list of user claims including:
  - `sub: 1234567890` (the user's subject identifier)
  - `name: John Doe`
  - `email: john.doe@example.com`
  - Additional OIDC standard claims (auth_time, idp, etc.)
- **Expected:** The claims page shows you're authenticated

**Step 4: Test logout**
- Click the "Logout" link in the navigation bar
- **Expected:** Logout page appears, then redirect to `https://localhost:5002/`
- **You should see:** No longer authenticated (accessing `/` will redirect to login again)

**Step 5: Verify logout completed properly**
- Try accessing `https://localhost:5002/` again
- **Expected:** Redirect back to login page at `https://localhost:5001/Auth/Login`
- **You should see:** Login form asking for credentials again (not auto-login)
- **Success:** This confirms logout worked: both client cookie and provider session were cleared

### Handling HTTPS Certificate Trust Issues

When developing and testing web applications locally, especially those configured to run over HTTPS, you may encounter browser warnings indicating that the SSL certificate is not trusted. This issue arises because the development certificates used by ASP.NET Core are self-signed and not issued by a recognized Certificate Authority (CA). Here's how you can address these warnings:

#### Trusting the ASP.NET Core Development Certificate

To eliminate these warnings and ensure a smooth development experience, trust the ASP.NET Core development certificate on your machine. Run the following command in your command line or terminal:

```bash
dotnet dev-certs https --trust
```

This command updates your system to trust the certificate used by ASP.NET Core during development. After running this command, restart your browsers to ensure the changes take effect.

:::warning[IMPORTANT]
**Special Note for Chrome Users**

Even after trusting the development certificate, some browsers like Chrome might still restrict access to sites using localhost for security reasons. If you encounter an error in Chrome stating that your connection is not private, you can bypass this by:

- Clicking anywhere on the error page and typing `thisisunsafe` or `badidea`, depending on the Chrome version. These keystrokes act as bypass commands in Chrome, allowing you to proceed to your localhost site.

It's important to use these bypasses sparingly and only in development scenarios, as they could mask genuine security issues in a production environment.
:::

### Observing OpenID Connect in Action

This testing phase offers a hands-on opportunity to see OpenID Connect in action within your client applications.
It covers the full cycle from initiating user authentication with the OpenID Provider (`OpenIDProviderApp`), through successful login, and back to the client application (`TestClientApp`) where the user's authentication information is used.

## Troubleshooting Common Issues

### Login redirect loop - keeps sending me back to login

**Symptom:** After entering credentials, you're redirected back to the login page instead of reaching the claims page.

**Cause:** The `request_uri` parameter isn't being passed through the login flow correctly.

**Fix:** Verify that:
1. Your Login GET action accepts `request_uri` from query parameters and passes it to the view
2. Your Login view includes a hidden input field with the `request_uri` value
3. Your Login POST action redirects back to `/connect/authorize?request_uri={requestUri}`

### Logout doesn't work - auto-logged back in immediately

**Symptom:** After logging out, accessing the client application logs you back in without showing the login form.

**Cause:** Only signing out from Cookie scheme, not OpenIdConnect scheme.

**Fix:** Your `EndSession` action must sign out from both schemes:
```csharp
return SignOut(
    CookieAuthenticationDefaults.AuthenticationScheme,
    OpenIdConnectDefaults.AuthenticationScheme);
```

### Redirect URI mismatch error

**Symptom:** Error message: "The redirect_uri in the request does not match a registered redirect URI"

**Cause:** The client configuration in `OpenIDProviderApp` doesn't include the URL that `TestClientApp` is using.

**Fix:** Verify that `RedirectUris` in the `ClientInfo` configuration matches exactly:
```csharp
RedirectUris = [new Uri("https://localhost:5002/signin-oidc", UriKind.Absolute)]
```

Port numbers and paths must match exactly: `5002` vs `5003` or `signin-oidc` vs `signin-callback` will fail.

### Invalid credentials - login always fails

**Symptom:** Login form keeps rejecting your credentials even when they're correct.

**Cause:** Typo in email or password, or the `TestUserStorage` wasn't configured correctly.

**Fix:** Double-check the credentials in `Program.cs` of `OpenIDProviderApp`:
- Email: `john.doe@example.com` (case-sensitive)
- Password: `Jd!2024$3cur3` (exact match required)

### Claims page shows "No claims available"

**Symptom:** After successful login, the claims page displays "No claims available. Are you authenticated?"

**Cause:** The `Index` action doesn't have the `[Authorize]` attribute, or authentication middleware isn't configured.

**Fix:** Ensure:
1. `Index` action has `[Authorize]` attribute
2. `Program.cs` includes `app.UseAuthentication()` and `app.UseAuthorization()` in the correct order
3. Both Cookie and OpenIdConnect authentication schemes are configured in `TestClientApp`

### Certificate trust warnings in browser

**Symptom:** Browser shows "Your connection is not private" or certificate warnings.

**Fix:** Trust the ASP.NET Core development certificate:
```bash
dotnet dev-certs https --trust
```

For Chrome specifically, if the warning persists, click anywhere on the error page and type `thisisunsafe`.

### Application won't start - port already in use

**Symptom:** `dotnet run` fails with "Failed to bind to address https://127.0.0.1:5001" (or 5002)

**Cause:** Another process is using the port, or previous instance didn't terminate.

**Fix:**
- Kill the process using the port (check Task Manager on Windows)
- Or change the port in `launchSettings.json` under `Properties` folder

### Access the Complete Solution on GitHub

If you encounter any issues or discrepancies while following this guide, or if you wish to verify your setup against a working model, the final state of the getting started solution is available on GitHub. You can clone the repository from [Abblix/Oidc.Server.GettingStarted](https://github.com/Abblix/Oidc.Server.GettingStarted) to access a fully implemented version of the OpenID Connect provider and client application as described in this guide. This resource is invaluable for troubleshooting, comparing your code, and understanding the complete implementation in context. It also serves as a quick reference to ensure that all configurations and code structures have been correctly followed and implemented.

## Conclusion

Congratulations on completing this guide!

You have made significant progress in understanding and implementing an OpenID Connect provider with ASP.NET MVC, using the Abblix OIDC Server solution.
You have successfully configured two applications: `OpenIDProviderApp` as the OpenID Connect provider and `TestClientApp` as the client or Relying Party.
Additionally, you have set up the essential services required for a secure OIDC flow.

By following this guide, you've built a working OpenID Connect provider on ASP.NET MVC: the foundation you can extend toward production-grade authentication.
This setup not only covers the basic configuration but also allows you to dive deeper into the details of managing user sessions, handling tokens, and ensuring user consent.

### Next Steps to Consider

- Building a browser SPA on top of this? The [React SPA with a .NET Backend-for-Frontend](https://www.abblix.com/en/docs/react-spa-bff-guide) guide walks the BFF build end to end, and [Authentication for SPAs with a BFF](https://www.abblix.com/en/docs/net-authentication-openid-connect-bff-spa) explains why the pattern keeps tokens out of the browser.
- Still weighing libraries? See [how Abblix compares to Duende IdentityServer and OpenIddict](https://www.abblix.com/en/docs/comparison-duende-openiddict), feature by feature.
- Apply the concepts learned to broaden the functionalities of your OpenID Connect provider. Try out different grant types, incorporate additional security measures, and tailor the user experience to better meet your needs.
- Use this foundation to experiment with new ideas and solutions in authentication technology.
- Continuously embrace best practices for maintaining a secure environment, such as securing secret storage, updating dependencies regularly, and adhering to the latest security protocols.
- Join platforms like GitHub, Stack Overflow, and the ASP.NET Core community forums. These platforms offer support, facilitate discussions, and help you discover new ideas and techniques.

From here, try swapping `MemoryCache` for Redis, or register a second client and watch the same flow adapt.
