C# tutorials > Frameworks and Libraries > Other Important Libraries > IdentityServer4/Duende IdentityServer for security (OpenID Connect, OAuth 2.0)

IdentityServer4/Duende IdentityServer for security (OpenID Connect, OAuth 2.0)

IdentityServer4/Duende IdentityServer for Security (OpenID Connect, OAuth 2.0)

IdentityServer4 (now Duende IdentityServer) is a popular open-source framework for ASP.NET Core that implements OpenID Connect and OAuth 2.0 protocols. These protocols provide a standardized way to authenticate users and authorize applications to access resources on their behalf. It is a crucial component for securing modern web applications, APIs, and mobile apps.

This tutorial will guide you through the fundamental concepts and demonstrate how to use IdentityServer4/Duende IdentityServer in your C# projects.

Introduction to OpenID Connect (OIDC) and OAuth 2.0

Understanding the Protocols

OAuth 2.0: An authorization framework that enables a third-party application to obtain limited access to an HTTP service on behalf of a resource owner. It defines roles like Resource Owner, Client, Authorization Server, and Resource Server.

OpenID Connect (OIDC): An authentication layer built on top of OAuth 2.0. It allows clients to verify the identity of the end-user based on the authentication performed by an authorization server, as well as to obtain basic profile information about the end-user in an interoperable and standardized manner. It introduces the concept of an 'ID Token' to provide user authentication information.

Key Concepts:

  • Client: An application that wants to access resources or authenticate a user.
  • Resource Owner: The user who owns the resources.
  • Authorization Server (IdentityServer): The server responsible for authenticating the user and issuing access tokens.
  • Resource Server (API): The server that hosts the protected resources and verifies access tokens.

Setting up a Duende IdentityServer Project

Creating a New ASP.NET Core Project

First, create a new ASP.NET Core Web API project. This will serve as our IdentityServer.

Installing the Duende IdentityServer NuGet Package

Install the necessary NuGet package. Note that IdentityServer4 has transitioned to Duende IdentityServer, requiring a commercial license for production use after a grace period. For development and testing purposes, it's perfectly acceptable to start with the free development license.

dotnet add package Duende.IdentityServer

Configuring IdentityServer

Startup Configuration

In the ConfigureServices method of your Startup.cs file, add the IdentityServer services. This involves:

  • Adding IdentityServer itself using AddIdentityServer().
  • Configuring in-memory stores for clients, identity resources, API scopes, and test users. (These are suitable for development, but you should use persistent stores like databases in production.)
  • Adding controllers with views if you need a UI for login, consent, etc.

In the Configure method, make sure to use the IdentityServer middleware using app.UseIdentityServer(), and ensure it is placed correctly within the middleware pipeline. Crucially, it needs to be placed after UseRouting() and before UseAuthorization().

// Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddIdentityServer()
        .AddInMemoryClients(Config.Clients)
        .AddInMemoryIdentityResources(Config.IdentityResources)
        .AddInMemoryApiScopes(Config.ApiScopes)
        .AddTestUsers(Config.TestUsers);

    services.AddControllersWithViews();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseStaticFiles();
    app.UseRouting();
    app.UseIdentityServer();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
    });
}

Defining Configuration (Clients, Resources, Scopes, Users)

Configuring Clients, Identity Resources, API Scopes, and Users

Create a Config.cs file to define the configurations for IdentityServer. Here's a breakdown of what's being configured:

  • Identity Resources: These represent sets of user claims. IdentityResources.OpenId() is mandatory for OpenID Connect. IdentityResources.Profile() adds standard profile claims like name and email.
  • API Scopes: These define the permissions that a client can request for accessing an API. For example, api1 is a scope that grants access to your API.
  • Clients: These represent the applications that will be using IdentityServer for authentication and authorization. Key properties include:
    • ClientId: A unique identifier for the client.
    • AllowedGrantTypes: Specifies the flow that the client will use (e.g., ClientCredentials for machine-to-machine communication, Code for interactive users).
    • ClientSecrets: A secret used to authenticate the client with IdentityServer.
    • AllowedScopes: The scopes that the client is allowed to request.
    • RedirectUris: Where the client will be redirected after a successful authentication. Used only for flows where the client interacts with the user (e.g. code flow).
    • PostLogoutRedirectUris: Where the client will be redirected after a logout. Used only for flows where the client interacts with the user (e.g. code flow).
  • Test Users: (For development only). Defines a set of test users that can be used for logging in. In a real-world application, you would typically integrate with a user database.

// Config.cs

using Duende.IdentityServer.Models;
using IdentityModel;
using System.Collections.Generic;
using Duende.IdentityServer;

public static class Config
{
    public static IEnumerable<IdentityResource> IdentityResources =>
        new IdentityResource[]
        {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile()
        };

    public static IEnumerable<ApiScope> ApiScopes =>
        new ApiScope[]
        {
            new ApiScope("api1", "My API")
        };

    public static IEnumerable<Client> Clients =>
        new Client[]
        {
            new Client
            {
                ClientId = "client",
                AllowedGrantTypes = GrantTypes.ClientCredentials,
                ClientSecrets = { new Secret("secret".Sha256()) },
                AllowedScopes = { "api1" }
            },
             new Client
            {
                ClientId = "mvc",
                ClientSecrets = { new Secret("secret".Sha256()) },

                AllowedGrantTypes = GrantTypes.Code,
                RedirectUris = { "https://localhost:5002/signin-oidc" },
                PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" },

                AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "api1" }
            }
        };

    public static List<TestUser> TestUsers =>
        new List<TestUser>
        {
            new TestUser
            {
                SubjectId = "1",
                Username = "alice",
                Password = "password"
            },
            new TestUser
            {
                SubjectId = "2",
                Username = "bob",
                Password = "password"
            }
        };
}

Protecting an API

Securing Your API with OAuth 2.0

To protect your API, you'll need to configure it to validate the access tokens issued by IdentityServer.

  1. Add JWT Bearer Authentication: In your API's Startup.cs, add JWT bearer authentication:
  2. 
    services.AddAuthentication("Bearer")
        .AddJwtBearer("Bearer", options =>
        {
            options.Authority = "https://localhost:5001"; // URL of your IdentityServer
            options.RequireHttpsMetadata = false; // Only for development!
            options.Audience = "api1"; // The API scope
        });
    
    services.AddAuthorization();
    
  3. Apply the [Authorize] Attribute: Add the [Authorize] attribute to your API controllers or actions that you want to protect.

With this configuration, any request to the protected API endpoint will require a valid access token issued by IdentityServer. The API will validate the token and extract the user's identity from it.

// API Controller

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authentication.JwtBearer;

namespace Api.Controllers
{
    [ApiController]
    [Route("[controller]")]
    [Authorize]
    public class IdentityController : ControllerBase
    {
        [HttpGet]
        public IActionResult Get()
        {
            return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
        }
    }
}

Acquiring Tokens (Client Credentials Flow)

Obtaining Access Tokens (Client Credentials Grant)

The client credentials grant type is suitable for machine-to-machine communication where a client application needs to access resources on its own behalf, without user interaction.

  1. Install IdentityModel: Add the IdentityModel NuGet package to your client application:
  2. dotnet add package IdentityModel
  3. Use the GetToken method to request an access token from IdentityServer.

// Example using IdentityModel library

using IdentityModel.Client;
using System.Net.Http;
using System.Threading.Tasks;

public static async Task<string> GetToken()
{
    // discover endpoints from metadata
    var client = new HttpClient();
    var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5001");
    if (disco.IsError)
    {
        Console.WriteLine(disco.Error);
        return null;
    }

    // request token
    var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
    {
        Address = disco.TokenEndpoint,
        ClientId = "client",
        ClientSecret = "secret",
        Scope = "api1"
    });

    if (tokenResponse.IsError)
    {
        Console.WriteLine(tokenResponse.Error);
        return null;
    }

    return tokenResponse.AccessToken;
}

Calling the API

Calling Your Protected API

Once you have obtained an access token, you can use it to make requests to your protected API.

  1. Set the Authorization Header: Add the Authorization header to your HTTP request with the Bearer scheme and the access token.
  2. Make the Request: Send the HTTP request to your API endpoint.

// Example calling the API with the token

using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

public static async Task CallApi(string accessToken)
{
    var client = new HttpClient();
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

    var response = await client.GetAsync("https://localhost:6001/identity"); // URL of your API
    if (!response.IsSuccessStatusCode)
    {
        Console.WriteLine(response.StatusCode);
    }
    else
    {
        var content = await response.Content.ReadAsStringAsync();
        Console.WriteLine(content);
    }
}

Real-Life Use Case Section

Scenario: Consider a microservices architecture where multiple services need to communicate with each other securely. IdentityServer4/Duende IdentityServer can be used to authenticate and authorize these services. Each service acts as a client, requesting tokens from IdentityServer and using those tokens to access other services.

Benefits:

  • Centralized Authentication: Manages all authentication logic in one place.
  • Improved Security: Enforces consistent security policies across all services.
  • Simplified Management: Makes it easier to manage users, clients, and permissions.

Best Practices

Security Considerations:

  • Use HTTPS: Always use HTTPS in production to protect sensitive data in transit.
  • Store Secrets Securely: Never store client secrets or other sensitive information in your code. Use environment variables or configuration files.
  • Validate Input: Validate all input to prevent security vulnerabilities like injection attacks.
  • Monitor Logs: Monitor your logs for suspicious activity.
  • Regularly Update: Keep IdentityServer4/Duende IdentityServer and its dependencies up-to-date to patch security vulnerabilities.
  • Use Production-Ready Storage: Don't use in-memory stores for clients, users, and configuration in production. Use a database (e.g., SQL Server, PostgreSQL) instead.
  • Implement Refresh Tokens: Consider using refresh tokens to allow clients to obtain new access tokens without requiring the user to re-authenticate.

Interview Tip

Common Interview Question: Explain the difference between authentication and authorization.

Answer: Authentication is the process of verifying the identity of a user or application. Authorization is the process of determining what resources a user or application has access to. IdentityServer4/Duende IdentityServer handles both authentication (via OpenID Connect) and authorization (via OAuth 2.0).

When to use them

  • Microservices Architectures: Centralized authentication and authorization for inter-service communication.
  • Single Sign-On (SSO): Enable users to authenticate once and access multiple applications.
  • API Security: Protect APIs from unauthorized access.
  • Mobile App Security: Securely authenticate users and authorize access to backend resources.
  • Third-Party Integrations: Allow third-party applications to access resources on behalf of users.

Memory footprint

The memory footprint of IdentityServer4/Duende IdentityServer depends on several factors, including the number of clients, users, and scopes, the size of the configuration data, and the number of concurrent requests.

To minimize the memory footprint, consider the following:

  • Use efficient data structures: Use optimized data structures for storing configuration data.
  • Cache frequently accessed data: Cache frequently accessed data, such as client configurations and user profiles.
  • Optimize database queries: Optimize database queries to reduce the amount of data retrieved.
  • Use compression: Use compression to reduce the size of data transmitted over the network.
  • Monitor memory usage: Monitor memory usage regularly to identify potential issues.

alternatives

Here are some alternatives to IdentityServer4/Duende IdentityServer for implementing security in your applications:

  • Auth0: A cloud-based identity and access management platform that provides features such as SSO, MFA, and user management.
  • Okta: Another cloud-based identity and access management platform that offers similar features to Auth0.
  • Keycloak: An open-source identity and access management solution that provides features such as SSO, identity brokering, and social login.
  • Azure Active Directory (Azure AD): A cloud-based identity and access management service that is part of the Microsoft Azure platform.
  • Custom Implementation: Building your own authentication and authorization system from scratch.

pros

Here are some advantages of using IdentityServer4/Duende IdentityServer for implementing security in your applications:

  • Standard-based: Implements industry-standard protocols such as OpenID Connect and OAuth 2.0, ensuring interoperability and security.
  • Flexible: Supports a wide range of authentication and authorization scenarios, including SSO, API security, and mobile app security.
  • Customizable: Can be customized to meet the specific needs of your application, such as adding custom claims or integrating with external identity providers.
  • Extensible: Can be extended with custom plugins and extensions to add new features or integrate with other systems.
  • Well-documented: Has comprehensive documentation and a large community of users, making it easy to learn and use.
  • Open Source (IdentityServer4): IdentityServer4 was open source, but Duende IdentityServer requires licensing after a trial period.

cons

Here are some disadvantages of using IdentityServer4/Duende IdentityServer for implementing security in your applications:

  • Complexity: Can be complex to set up and configure, especially for those who are new to identity and access management.
  • Performance Overhead: Adds a performance overhead to your application, as each request must be authenticated and authorized.
  • Licensing Costs (Duende IdentityServer): Duende IdentityServer requires a commercial license for production use, which can be expensive for some organizations.
  • Learning Curve: Requires a significant investment of time and effort to learn and master.
  • Potential Security Risks: If not configured and maintained properly, IdentityServer4/Duende IdentityServer can introduce security vulnerabilities into your application.

FAQ

  • What is the difference between IdentityServer4 and Duende IdentityServer?

    IdentityServer4 was the original open-source framework. Duende IdentityServer is the commercial successor, offering enhanced features, support, and requiring licensing for production use.
  • Is IdentityServer4 free to use?

    IdentityServer4 is open source and free to use. However, it is no longer actively maintained. Duende IdentityServer requires a commercial license for production deployments.
  • What are the different grant types in OAuth 2.0?

    OAuth 2.0 defines several grant types, including authorization code, implicit, resource owner password credentials, and client credentials. Each grant type is suitable for different scenarios.
  • How do I secure my API with IdentityServer4/Duende IdentityServer?

    You can secure your API by configuring it to validate the access tokens issued by IdentityServer4/Duende IdentityServer. This typically involves adding JWT bearer authentication to your API and applying the [Authorize] attribute to your controllers or actions.