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 in under an hour using 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
- 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 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:
dotnet new sln -n GettingStarted -
Create the first project,
OpenIDProviderApp:dotnet new mvc -n OpenIDProviderApp -
Create the second project,
TestClientApp:dotnet new mvc -n TestClientApp -
Add both projects to the solution:
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 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:
{
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:5001",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
} -
For TestClientApp:
{
"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 using the command line, follow these steps:
- Open a terminal.
- Navigate to your project directory. Use the
cdcommand to change to the directory where your project is located. - Run the following command to install the Abblix.Oidc.Server.Mvc package:
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
usingdirectives for the necessary namespaces: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
builderinstance 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:
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:
{
"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" ]
}
]
}
}
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:
builder.Services.AddOidcServices(options => { ... });
- This line adds and configures the services necessary for the Abblix OIDC Server to operate within your application.
- The
AddOidcServicesextension method takes a lambda expression where you can specify various options to tailor the OIDC server's behavior.
Configuring Clients:
"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 inProgram.csloads 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.
"ClientSecrets": [ { "Sha512Hash": "vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==" } ],
The value is the Base64-encoded 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:
echo -n 'secret' | openssl dgst -sha512 -binary | base64
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:
"AllowedGrantTypes": [ "authorization_code" ],
This code specifies the grant types the client can use. Here, the client is configured to use the Authorization Code Flow.
The Authorization Code flow is a secure 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, 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, and optionally, a 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 Auth and Proof Key for Code Exchange:
"TokenEndpointAuthMethod": "client_secret_post",
"PkceRequired": true,
TokenEndpointAuthMethoddetermines how the client application authenticates itself at the token endpoint. Settingclient_secret_postspecifies that the client application includes theclient_idandclient_secretin the body of a POST request to the token endpoint.PkceRequiredspecifies whether Proof Key for Code Exchange (PKCE) is required. PKCE enhances the security of the Authorization Code flow.
Setting PkceRequired to false might be suitable for trusted or internal clients but is strongly recommended to be true for public clients.
Redirect URIs:
"RedirectUris": [ "https://localhost:5002/signin-oidc" ],
"PostLogoutRedirectUris": [ "https://localhost:5002/signout-callback-oidc" ],
RedirectUrisare the URLs to which the OIDC server can send responses (tokens or authorization codes) after authenticating the user.PostLogoutRedirectUrisdefine where the user is redirected after logging out. These must be pre-registered to prevent 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:
options.LoginUri = new Uri("/Auth/Login", UriKind.Relative);
LoginUrispecifies 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:
options.SigningKeys = [JsonWebKeyFactory.CreateRsa(PublicKeyUsages.Signature)];
SigningKeysincludes cryptographic keys used for signing the tokens issued by your OIDC server. Specifically, theJsonWebKeyFactory.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:
builder.Services
.AddAuthentication()
.AddCookie();
Explanation
AddAuthentication Method:
.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:
.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 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). 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), and 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, 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:
builder.Services.AddDistributedMemoryCache();
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:
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 whenPkceRequired = truebecause 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 arequest_uriparameter 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_uriparameter. 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 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:
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:
// 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.
<!-- 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 theLoginPOST method inAuthController. - Input fields for
emailandpasswordare marked withrequired, making sure users cannot submit the form without filling out these fields. - The
@Html.ValidationSummaryhelper method is used to display any validation errors that occur during the login process. - A hidden input field named
requestUrimaintains the continuity of the login process by preserving therequest_urivalue 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.
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:
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.
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, Bcrypt, or 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.
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>();
The AddAlias method is part of the Abblix.DependencyInjection package. To use this method, ensure you include the appropriate namespace in your file:
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.
// 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 givenPath.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:
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:
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:
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 afterUseRouting()and beforeUseAuthorization()- 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:
"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:
"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: TheClientIdis a unique identifier for the application registered with the OpenID Connect provider, while theClientSecretis 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 tofalseavoids 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 totrueenhances the security of the Authorization Code flow.GetClaimsFromUserInfoEndpoint: Instructs the application to retrieve additional claims about the user from the 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:
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:
@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 session.
Add the EndSession action to HomeController
File: Controllers/HomeController.cs
Add the following using statements at the top of the file:
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
Then add the EndSession action to your HomeController:
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/endsessionon the provider - Ensures the user is logged out from the identity provider itself
- Handles the redirect back to
PostLogoutRedirectUriafter 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:
<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
OpenIDProviderAppandTestClientApp. - In each terminal, navigate to the respective project directory of each application.
- Run the following command in both terminals:
This command starts each application with HTTPS enabled, which is necessary for OpenID Connect operations.
dotnet run -lp https
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 Doeemail: 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:
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.
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
thisisunsafeorbadidea, 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:
- Your Login GET action accepts
request_urifrom query parameters and passes it to the view - Your Login view includes a hidden input field with the
request_urivalue - 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:
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:
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:
Indexaction has[Authorize]attributeProgram.csincludesapp.UseAuthentication()andapp.UseAuthorization()in the correct order- 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:
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.jsonunderPropertiesfolder
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 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
- 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.