Найти в Дзене

OpenID Connect: подключение к Keycloak 26 с помощью Duende.IdentityModel 8 и пары "логин-пароль"

В предшествующей статье я рассказал, как дополнил код сервиса ASP.NET Core 8 Web API, чтобы выполнялась авторизация по предоставленному токену доступа (JWT). Теперь посмотрим, как в обычном приложении .NET Core можно подключиться к Keycloak и раздобыть эти самые токены. Использовать буду библиотеку Duende.IdentityModel из одноименного NuGet-пакета. Странно, но альтернатив для C# / .NET Core на данный момент вроде как и нет. По крайней мере, для OpenID Connect - не нашел. Итак. Скопировал этот класс откуда-то из документации, но забыл откуда: public static class DangerousHttpClientFactory
{
public static HttpClient Create()
{
HttpClientHandler handler = new();
handler.ServerCertificateCustomValidationCallback = (_, _, _, _) => true; // Not a best way to create an HttpClient instance, check the official guidelines instead: // https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines
return new HttpClient(handler);

В предшествующей статье я рассказал, как дополнил код сервиса ASP.NET Core 8 Web API, чтобы выполнялась авторизация по предоставленному токену доступа (JWT). Теперь посмотрим, как в обычном приложении .NET Core можно подключиться к Keycloak и раздобыть эти самые токены.

Использовать буду библиотеку Duende.IdentityModel из одноименного NuGet-пакета. Странно, но альтернатив для C# / .NET Core на данный момент вроде как и нет. По крайней мере, для OpenID Connect - не нашел.

Итак. Скопировал этот класс откуда-то из документации, но забыл откуда:

public static class DangerousHttpClientFactory
{
public static HttpClient Create()
{
HttpClientHandler handler = new();
handler.ServerCertificateCustomValidationCallback = (_, _, _, _) => true;
// Not a best way to create an HttpClient instance, check the official guidelines instead:
// https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines
return new HttpClient(handler);
}
}

Теперь код из тестового консольного приложения:

HttpClient client = DangerousHttpClientFactory.Create();
DiscoveryDocumentResponse disco = await client.GetDiscoveryDocumentAsync("https://<IP/DNS-имя сервера Keycloak>:<порт>/realms/<имя зоны>/");
if (disco.IsError)
{
if (disco.Error != null)
Console.WriteLine(disco.Error);
if (disco.Exception != null)
Console.WriteLine(disco.Exception);
return;
}

Как показано в этом коде, рекомендуется начинать с чтения основных параметров подключения с сервера. Далее:

// request token
TokenResponse tokenResponse = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "ArbitrIntercluster",
ClientSecret = clientSecret,
Scope = "user_roles offline_access",
UserName = "admin",
Password = password
});
// Handle the response
if (tokenResponse.IsError)
{
Console.WriteLine($"Error: {tokenResponse.Error}");
Console.WriteLine($"Error Description: {tokenResponse.ErrorDescription}");
return;
}
// Use the token
Console.WriteLine("Successfully retrieved token:");
Console.WriteLine($"Access Token: {tokenResponse.AccessToken}");
Console.WriteLine($"Refresh Token: {tokenResponse.RefreshToken ?? "None"}");
// Refresh token requires "offline_access" scope
Console.WriteLine($"Expires In: {tokenResponse.ExpiresIn} seconds");

ClientId, ClientSecret, Scope - это все берется из настроек в Keycloak Client / Client Scope. Scope - это список имен Keycloak Client Scope, разделенных пробелами.

В общем, тут реально все просто. Еще можно использовать токен обновления:

-2
if (tokenResponse.RefreshToken != null)
{
// а вот возьмем и обновим
tokenResponse = await client.RequestRefreshTokenAsync(new RefreshTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "ArbitrIntercluster",
ClientSecret = clientSecret,
Scope = "user_roles offline_access",
RefreshToken = tokenResponse.RefreshToken
});
// Handle the response
if (tokenResponse.IsError)
{
Console.WriteLine($"Error: {tokenResponse.Error}");
Console.WriteLine($"Error Description: {tokenResponse.ErrorDescription}");
return;
}
// Use the token
Console.WriteLine("Successfully retrieved token:");
// потенциально обновляются оба, и токен доступа (обязательно), и токен обновления (необязательно)
Console.WriteLine($"Access Token: {tokenResponse.AccessToken}");
Console.WriteLine($"Refresh Token: {tokenResponse.RefreshToken ?? "None"}");
// Refresh token requires "offline_access" scope
Console.WriteLine($"Expires In: {tokenResponse.ExpiresIn} seconds");
}

Теперь tokenResponse.AccessToken можно спокойно, как есть (в виде строки), передавать в заголовке Bearer для доступа к сервису Web API.

Visual Studio 2022 (по крайне мере, текущая версия) умеет показывать содержимое токена - достаточно нажать кнопку View в просмотре поля AccessToken в отладчике (Text Visualizer):

-3

На этом скриншоте как раз видно секцию roles, которая будет отображена в роли пользователя в сервисе Web API.

Ну а про настройку Keycloak я собираюсь рассказать в следующей статье.