Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Sdk client vNext #81

Merged
merged 6 commits into from
May 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions KeycloakAuthorizationServicesDotNet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Keycloak.AuthServices.Authe
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Keycloak.AuthServices.Common.Tests", "tests\Keycloak.AuthServices.Common.Tests\Keycloak.AuthServices.Common.Tests.csproj", "{8F9E1322-568B-4F02-A1DD-4222C8457A42}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DependsOnNuGetSource", "samples\DependsOnNuGetSource\DependsOnNuGetSource.csproj", "{82DB0FDE-316D-4741-B335-8B7B36DBE962}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthorizationGettingStarted", "samples\AuthorizationGettingStarted\AuthorizationGettingStarted.csproj", "{D64B4098-165B-48AA-BE07-B9E9963E0CB5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GettingStarted", "samples\GettingStarted\GettingStarted.csproj", "{671BA3B1-DBF2-4161-97B5-433B91A3730E}"
Expand Down Expand Up @@ -124,10 +122,6 @@ Global
{8F9E1322-568B-4F02-A1DD-4222C8457A42}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8F9E1322-568B-4F02-A1DD-4222C8457A42}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8F9E1322-568B-4F02-A1DD-4222C8457A42}.Release|Any CPU.Build.0 = Release|Any CPU
{82DB0FDE-316D-4741-B335-8B7B36DBE962}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{82DB0FDE-316D-4741-B335-8B7B36DBE962}.Debug|Any CPU.Build.0 = Debug|Any CPU
{82DB0FDE-316D-4741-B335-8B7B36DBE962}.Release|Any CPU.ActiveCfg = Release|Any CPU
{82DB0FDE-316D-4741-B335-8B7B36DBE962}.Release|Any CPU.Build.0 = Release|Any CPU
{D64B4098-165B-48AA-BE07-B9E9963E0CB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D64B4098-165B-48AA-BE07-B9E9963E0CB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D64B4098-165B-48AA-BE07-B9E9963E0CB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -166,7 +160,6 @@ Global
{A85B6B1E-2030-47BE-9B9F-F645B08E501D} = {96857509-627A-4FD2-AC82-34387619A7B1}
{FE34728A-25AA-44E1-A3A6-AB500307C406} = {96857509-627A-4FD2-AC82-34387619A7B1}
{8F9E1322-568B-4F02-A1DD-4222C8457A42} = {96857509-627A-4FD2-AC82-34387619A7B1}
{82DB0FDE-316D-4741-B335-8B7B36DBE962} = {AEBE10B1-96B1-4060-B8C1-1F9BFA7A586C}
{D64B4098-165B-48AA-BE07-B9E9963E0CB5} = {AEBE10B1-96B1-4060-B8C1-1F9BFA7A586C}
{671BA3B1-DBF2-4161-97B5-433B91A3730E} = {AEBE10B1-96B1-4060-B8C1-1F9BFA7A586C}
{7499F9F0-1132-46B4-AAA2-D60D9F113293} = {96857509-627A-4FD2-AC82-34387619A7B1}
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ app.Run();

`dotnet cake --target build`

`dotnet cake --target test`

`dotnet pack -o ./Artefacts`

## Blog Posts
Expand Down
46 changes: 44 additions & 2 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ export default withMermaid({
title: "Keycloak.AuthServices",
description: "",
base: '/keycloak-authorization-services-dotnet/',
head: [
["link", { rel: "icon", type: "image/png", sizes: "16x16", href: "/favicon-16x16.png" }],
["link", { rel: "icon", type: "image/png", sizes: "32x32", href: "/favicon-32x32.png" }],
["link", { rel: "manifest", href: "/site.webmanifest" }]
],
themeConfig: {
logo: '/logo.svg',
// https://vitepress.dev/reference/default-theme-config
Expand Down Expand Up @@ -38,8 +43,45 @@ export default withMermaid({
text: 'Authorization',
collapsed: false,
items: [
{ text: 'Authorization Server', link: '/authorization/authorization-server' },
{ text: 'Protected Resources ✨', link: '/authorization/resources' },
{
text: 'Authorization Server', link: '/authorization/authorization-server'
},
{
text: 'Protected Resources ✨', link: '/authorization/resources',
collapsed: true,
items: [
{ text: 'ASP.NET Core Integration', link: '/authorization/resources-api' },
{ text: 'Use HTTP Client', link: '/authorization/resources-client' },
{ text: 'Client API Reference', link: '/authorization/resources-client-reference' },
]
},
]
},
{
text: 'Admin REST API ⚙️',
collapsed: true,
items: [
{ text: 'Introduction', link: '/admin-rest-api/admin-rest-api' },
{ text: 'Access Token Management', link: '/admin-rest-api/access-token' },
{
text: 'Admin API Reference', link: '/admin-rest-api/admin-api-reference',
items: [
{ text: 'Realm Client', link: '/admin-rest-api/realm-client' },
{ text: 'User Client', link: '/admin-rest-api/user-client' },
{ text: 'Group Client', link: '/admin-rest-api/group-client' },
]
},
{ text: 'OpenAPI Support', link: '/admin-rest-api/admin-api-openapi' },
]
},
{
text: 'Protection API ⚙️',
collapsed: true,
items: [
{ text: 'Introduction', link: '/protection-api/protection-api' },
{
text: 'Protection API Reference', link: '/protection-api/protection-api-reference',
},
]
},
{
Expand Down
3 changes: 0 additions & 3 deletions docs/admin-rest-api.md

This file was deleted.

46 changes: 46 additions & 0 deletions docs/admin-rest-api/access-token.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Access Token Management

Keycloak comes with a fully functional Admin REST API with all features provided by the Admin Console. To invoke the API you need to obtain an access token with the appropriate permissions.

Please refer to [official docs](https://www.keycloak.org/docs/latest/server_development/#authenticating-with-a-service-account) for more details on how to setup Service Account.

## Configure Service Account

We need to create a *Service Account* in **master** realm, configure a special *Audience Mapper* that adds **security-admin-console** audience to the token, and assign *Service Account Role* - **"admin"**.

Create a service account client called **"admin-api"** and enable *Client Authentication* and *Service Account Roles*.

Then, [download adapter config](/configuration/configuration-keycloak#download-adapter-config) from Keycloak and added it to "appsettings.json" to "Keycloak section. Here is how it looks like:

```json
{
"Keycloak": {
"realm": "master",
"auth-server-url": "http://localhost:8080/",
"ssl-required": "none",
"resource": "admin-api",
"credentials": {
"secret": "k9LYTWKfbNOyfzFt2ZZsFl3Z4x4aAecf"
},
"confidential-port": 0
}
}
```

💡 See [admin-api export file](/admin-rest-api/admin-api.spec) if you want to import it and see how it looks in Keycloak.

## Add Token Management

Luckily, there is a production-ready library called [DuendeSoftware/Duende.AccessTokenManagement](https://github.com/DuendeSoftware/Duende.AccessTokenManagement) that retrieves and caches tokens.

To install it run:

```bash
dotnet add package Duende.AccessTokenManagement
```

See [the docs](https://github.com/DuendeSoftware/Duende.AccessTokenManagement/wiki/worker-applications) on how to configure and use this library to use Service Accounts.

### Example

<<< @/../tests/Keycloak.AuthServices.IntegrationTests/Admin/KeycloakRealmClientTests.cs#GetRealmAsync_RealmExists_Success {3-14,18 cs:line-numbers}
7 changes: 7 additions & 0 deletions docs/admin-rest-api/admin-api-openapi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# OpenAPI Support <Badge type="warning" text="preview" />

From Keycloak documentation:

> The OpenAPI definitions are a feature that is currently in preview. Please provide your feedback by [joining this discussion](https://github.com/keycloak/keycloak/discussions/8898) while we’re continuing to work on this. If you find something is outdated or wrong, create a GitHub issue and provide a pull request.

It means we can use OpenAPI definitions to generate full-fledged API.
14 changes: 14 additions & 0 deletions docs/admin-rest-api/admin-api-reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Admin API Reference

[Keycloak.AuthServices.Sdk](https://www.nuget.org/packages/Keycloak.AuthServices.Sdk/) provides a basic support for common and most popular API endpoints.

The full API documentation - <https://www.keycloak.org/docs-api/latest/rest-api/>.

>[!IMPORTANT]
> **Keycloak.AuthServices** is an open source project with limited developer capacity. Many API endpoints might be missing. But **contributions are encouraged**!
>
> [![contributionswelcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/nikiforovall/keycloak-authorization-services-dotnet)

💡**Alternatively**, you may want to use OpenAPI definition for Keycloak to generate a client based on your generator of choice - see [OpenAPI Support](/admin-rest-api/admin-api-openapi) for more details.

<<< @/../src/Keycloak.AuthServices.Sdk\Admin\IKeycloakClient.cs
120 changes: 120 additions & 0 deletions docs/admin-rest-api/admin-api.spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# admin-api (Service Client)

Here is an configuration file for admin-api client. Note, you might need to assign master realm roles after export separately.

```json
{
"clientId": "admin-api", // [!code highlight]
"name": "",
"description": "",
"rootUrl": "",
"adminUrl": "",
"baseUrl": "",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"secret": "k9LYTWKfbNOyfzFt2ZZsFl3Z4x4aAecf", // [!code highlight]
"redirectUris": [
"/*"
],
"webOrigins": [
"/*"
],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
"standardFlowEnabled": true,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": true,
"serviceAccountsEnabled": true,
"publicClient": false,
"frontchannelLogout": true,
"protocol": "openid-connect",
"attributes": {
"oidc.ciba.grant.enabled": "false",
"oauth2.device.authorization.grant.enabled": "false",
"client.secret.creation.time": "1714665890",
"backchannel.logout.session.required": "true",
"backchannel.logout.revoke.offline.tokens": "false"
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": true,
"nodeReRegistrationTimeout": -1,
"protocolMappers": [
{
"name": "Client IP Address",
"protocol": "openid-connect",
"protocolMapper": "oidc-usersessionmodel-note-mapper",
"consentRequired": false,
"config": {
"user.session.note": "clientAddress",
"introspection.token.claim": "true",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "clientAddress",
"jsonType.label": "String"
}
},
{
"name": "Audience",
"protocol": "openid-connect",
"protocolMapper": "oidc-audience-mapper",
"consentRequired": false,
"config": {
"included.client.audience": "security-admin-console", // [!code highlight]
"id.token.claim": "false",
"lightweight.claim": "false",
"introspection.token.claim": "true",
"access.token.claim": "true"
}
},
{
"name": "Client ID",
"protocol": "openid-connect",
"protocolMapper": "oidc-usersessionmodel-note-mapper",
"consentRequired": false,
"config": {
"user.session.note": "client_id",
"introspection.token.claim": "true",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "client_id",
"jsonType.label": "String"
}
},
{
"name": "Client Host",
"protocol": "openid-connect",
"protocolMapper": "oidc-usersessionmodel-note-mapper",
"consentRequired": false,
"config": {
"user.session.note": "clientHost",
"introspection.token.claim": "true",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "clientHost",
"jsonType.label": "String"
}
}
],
"defaultClientScopes": [
"web-origins",
"acr",
"profile",
"roles",
"email"
],
"optionalClientScopes": [
"address",
"phone",
"offline_access",
"microprofile-jwt"
],
"access": {
"view": true,
"configure": true,
"manage": true
}
}
```
81 changes: 81 additions & 0 deletions docs/admin-rest-api/admin-rest-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# HTTP Admin REST API

[Keycloak.AuthServices.Sdk](https://www.nuget.org/packages/Keycloak.AuthServices.Sdk) provides a typed HTTP Client to work with Keycloak Admin HTTP REST API.


The Admin REST API in Keycloak provides a programmatic way to manage and administer Keycloak instances. It allows you to perform various administrative tasks such as creating and managing realms, users, roles, clients, and more. To interact with the Admin REST API, you can use HTTP requests to send commands and retrieve data. The API follows the REST architectural style and is designed to be simple and intuitive to use.

> [!NOTE]
> See full list of API endpoints - [Admin REST API](https://www.keycloak.org/docs-api/latest/rest-api/#_overview)

Keycloak provides a comprehensive set of endpoints that cover a wide range of administrative operations. These endpoints are organized into different resource types, such as realms, users, roles, and clients, making it easy to navigate and manipulate the Keycloak configuration.

❗ To get started with the Admin REST API, you need to authenticate and obtain an access token. Once you have the token, you can include it in the Authorization header of your HTTP requests to authenticate and authorize your API calls.

> [!NOTE]
> See [Admin REST API - Server Development](https://www.keycloak.org/docs/latest/server_development/#admin-rest-api) documentation for more details.

## Add to your code

Install [Keycloak.AuthServices.Sdk](https://www.nuget.org/packages/Keycloak.AuthServices.Sdk):

```bash
dotnet add package Keycloak.AuthServices.Sdk
```

> [!IMPORTANT]
> Admin API is protected so you need to acquire access token somehow. See [Access Token Management](/admin-rest-api/access-token)

You can use `IKeycloakClient` from Web APIs, Worker, Console apps, etc. It is fully integrated with [IHttpClientFactory](https://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory) and therefore you don't need to worry about `HttpClient` lifetime and the way you work with it.

To add it to DI, you can use convenience extensions method `AddKeycloakAdminHttpClient`:

```csharp
public static IHttpClientBuilder AddKeycloakAdminHttpClient(
this IServiceCollection services,
KeycloakAdminClientOptions keycloakAdminClientOptions,
Action<HttpClient>? configureClient = default
)
```

It registers typed client with umbrella interface `IKeycloakClient` and adds `KeycloakAdminClientOptions` to DI so you can use it as `IOptions<KeycloakAdminClientOptions>` in your code.

> [!NOTE]
> 💡 `AddKeycloakAdminHttpClient` returns `IHttpClientBuilder` so you can proceed and configure underlying `HttpClient`.
>
> For example, here is how to add Polly and some custom delegating handlers:
>```csharp
> services
> .AddKeycloakAdminHttpClient(configuration)
> .AddStandardResilienceHandler()
> .AddHttpMessageHandler<TimingHandler>()
> .AddHttpMessageHandler<ValidateHeaderHandler>();
>```

## Console App

Here is how to use it from a Console App:

```csharp
var services = new ServiceCollection();

var keycloakOptions = new KeycloakAdminClientOptions
{
AuthServerUrl = "http://localhost:8080/",
Realm = "master",
Resource = "admin-api",
};
services.AddKeycloakAdminHttpClient(keycloakOptions);

var sp = services.BuildServiceProvider();
var client = sp.GetRequiredService<IKeycloakClient>();

var realm = await client.GetRealmAsync("Test");
```

> [!WARNING]
> In the code above the **key part** is missing - Authentication and Authorization. Because of that, you will receive **401 (Unauthorized)**. In the [next section](/admin-rest-api/access-token) I will show you how to obtain access token and successfully invoke Admin API endpoints.

Here is `IKeycloakClient`:

<<< @/../src/Keycloak.AuthServices.Sdk\Admin\IKeycloakClient.cs
3 changes: 3 additions & 0 deletions docs/admin-rest-api/group-client.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# IKeycloakGroupClient

<<< @/../src/Keycloak.AuthServices.Sdk/Admin/IKeycloakGroupClient.cs
3 changes: 3 additions & 0 deletions docs/admin-rest-api/realm-client.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# IKeycloakRealmClient

<<< @/../src/Keycloak.AuthServices.Sdk/Admin/IKeycloakRealmClient.cs
3 changes: 3 additions & 0 deletions docs/admin-rest-api/user-client.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# IKeycloakUserClient

<<< @/../src/Keycloak.AuthServices.Sdk/Admin/IKeycloakUserClient.cs
Loading
Loading