Accessing Users from IdentityServer in .NET Core

By FoxLearn 1/10/2025 7:31:21 AM   27
If you ever find yourself needing to directly access the IdentityServer users database, you might notice that the API can feel somewhat awkward.

This is because the methods for direct data access are quite low-level, relying on HttpClient extension methods.

Install the Required NuGet Packages

Before diving into code, you'll need to install the following NuGet packages:

  • IdentityModel: Provides extension methods for interacting with IdentityServer.
  • Microsoft.Extensions.Http: Includes HttpClient and IHttpClientFactory, which are essential for making HTTP requests.

You can install these packages via NuGet Package Manager or using the following commands:

dotnet add package IdentityModel
dotnet add package Microsoft.Extensions.Http

Create an AccessToken Repository

IdentityServer offers two types of access: client access and user access. For our purpose, we’ll be using client access, which grants permission to access all users. To use client access, you’ll need the following information:

  • The URL of your IdentityServer.
  • A client ID (a readable string).
  • A client secret (typically an unreadable GUID-like string).

Below is the code for creating an AccessTokenRepository that will handle obtaining a client access token:

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

namespace IdentityServer.Repositories
{
    public class AccessTokenRepository 
    {
        private readonly IHttpClientFactory _httpClientFactory;

        public AccessTokenRepository(IHttpClientFactory httpClientFactory)
        {
            _httpClientFactory = httpClientFactory;
        }

        public async Task<string> GetClientAccessTokenAsync()
        {
            var tokenRequest = new ClientCredentialsTokenRequest
            {
                Address = $"[IdentityServerUrl]/connect/token",
                ClientId = "[IdentityServer Client ID]",
                ClientSecret = "[IdentityServer Client Secret]"
            };

            var client = _httpClientFactory.CreateClient("HttpClient");
            var response = await client.RequestClientCredentialsTokenAsync(tokenRequest);

            if (response.IsError)
                throw new Exception($"{GetType()}.GetClientAccessToken failed: {response.ErrorDescription} ({response.HttpStatusCode})");

            return response.AccessToken;
        }
    }
}

In this example:

  • ClientCredentialsTokenRequest: This object is used to request an access token from IdentityServer using the client ID and client secret.
  • RequestClientCredentialsTokenAsync: This method makes the actual request for the token.

Create a User Repository

Once you have the client access token, you're ready to interact with the IdentityServer database and retrieve user information.

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

namespace IdentityServer.Repositories
{
    public class UserRepository
    {
        private readonly IHttpClientFactory _httpClientFactory;
        private readonly AccessTokenRepository _accessTokenRepository;

        public UserRepository(IHttpClientFactory httpClientFactory, AccessTokenRepository accessTokenRepository)
        {
            _httpClientFactory = httpClientFactory;
            _accessTokenRepository = accessTokenRepository;
        }

        public async Task<string?> GetUserAsync(string username)
        {
            var client = _httpClientFactory.CreateClient("HttpClient");
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", await _accessTokenRepository.GetClientAccessTokenAsync());

            var responseMessage = await client.GetAsync($"[IdentityServerUrl]/localapi/users/{username}");

            if (responseMessage.StatusCode == HttpStatusCode.NotFound)
                return null;

            if (responseMessage.StatusCode == HttpStatusCode.Unauthorized)
                throw new Exception("Unauthorized");

            if (!responseMessage.IsSuccessStatusCode)
                throw new Exception($"{responseMessage.StatusCode}");

            var userJson = await responseMessage.Content.ReadAsStringAsync();
            return userJson;
        }
    }
}

In this example:

  • GetUserAsync: This method sends an HTTP request to fetch user data based on the username.
  • Authorization header: It attaches the client access token to the request header to authenticate the API call.

The data returned is a raw JSON string from IdentityServer. You'll need to parse it according to your needs, especially if you are using a custom user database format.

Set Up Dependency Injection and Use the Repositories

 Now that we have our repositories in place, we need to configure dependency injection (DI) to make the HttpClient, AccessTokenRepository, and UserRepository available throughout the application.

In your Program.cs or Startup.cs, add the following code:

builder.Services.AddHttpClient("HttpClient");
builder.Services.AddSingleton<AccessTokenRepository>();
builder.Services.AddSingleton<UserRepository>();

Once everything is set up, you can use the UserRepository to fetch user information:

if (await _userRepository.GetUserAsync(userName) == null)
    return BadRequest("User not found.");

This example checks if a user exists in IdentityServer. If not, it returns a bad request response.

By following this guide, you can securely interact with your IdentityServer and retrieve user data in c#.