Skip to content

Commit

Permalink
feat: Add Aspire Hosting package (#111)
Browse files Browse the repository at this point in the history
* feat: Add Keycloak.AuthServices.Aspire.Hosting package
* Add Keycloak.AuthServices.Templates package
* Add docs
  • Loading branch information
NikiforovAll authored Jun 2, 2024
1 parent 7e6aa0c commit 940edad
Show file tree
Hide file tree
Showing 66 changed files with 6,786 additions and 25 deletions.
8 changes: 7 additions & 1 deletion dotnet-tools.json → .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
"version": 1,
"isRoot": true,
"tools": {
"centralisedpackageconverter": {
"version": "1.0.52",
"commands": [
"central-pkg-converter"
]
},
"cake.tool": {
"version": "2.0.0",
"commands": [
Expand All @@ -15,4 +21,4 @@
]
}
}
}
}
14 changes: 14 additions & 0 deletions KeycloakAuthorizationServicesDotNet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Keycloak.AuthServices.OpenT
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApp", "samples\WebApp\WebApp.csproj", "{4AF4CE52-F007-4FEE-9324-7E52314398FF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Keycloak.AuthServices.Aspire.Hosting", "src\Keycloak.AuthServices.Aspire.Hosting\Keycloak.AuthServices.Aspire.Hosting.csproj", "{0943D3CE-5B15-46F8-9B0C-0C2911FD70A3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Keycloak.AuthServices.Templates", "src\Keycloak.AuthServices.Templates\Keycloak.AuthServices.Templates.csproj", "{EFF07507-0C96-49BD-8D66-0E2B05DA0BDD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -172,6 +176,14 @@ Global
{4AF4CE52-F007-4FEE-9324-7E52314398FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4AF4CE52-F007-4FEE-9324-7E52314398FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4AF4CE52-F007-4FEE-9324-7E52314398FF}.Release|Any CPU.Build.0 = Release|Any CPU
{0943D3CE-5B15-46F8-9B0C-0C2911FD70A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0943D3CE-5B15-46F8-9B0C-0C2911FD70A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0943D3CE-5B15-46F8-9B0C-0C2911FD70A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0943D3CE-5B15-46F8-9B0C-0C2911FD70A3}.Release|Any CPU.Build.0 = Release|Any CPU
{EFF07507-0C96-49BD-8D66-0E2B05DA0BDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EFF07507-0C96-49BD-8D66-0E2B05DA0BDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EFF07507-0C96-49BD-8D66-0E2B05DA0BDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EFF07507-0C96-49BD-8D66-0E2B05DA0BDD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -200,6 +212,8 @@ Global
{B060EE8C-C76D-48A4-B209-4646070A7E0D} = {AEBE10B1-96B1-4060-B8C1-1F9BFA7A586C}
{3FE98A91-BA4E-4D4F-A6A5-A43123644ACD} = {F9D5C5B8-9933-4AE0-ADAC-6B8C15F7552A}
{4AF4CE52-F007-4FEE-9324-7E52314398FF} = {AEBE10B1-96B1-4060-B8C1-1F9BFA7A586C}
{0943D3CE-5B15-46F8-9B0C-0C2911FD70A3} = {F9D5C5B8-9933-4AE0-ADAC-6B8C15F7552A}
{EFF07507-0C96-49BD-8D66-0E2B05DA0BDD} = {F9D5C5B8-9933-4AE0-ADAC-6B8C15F7552A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E1907BFD-C144-4B48-AA40-972F499D4E08}
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ Easy Authentication and Authorization with Keycloak in .NET.
| `Keycloak.AuthServices.Authorization` | [![Nuget](https://img.shields.io/nuget/v/Keycloak.AuthServices.Authorization.svg)](https://nuget.org/packages/Keycloak.AuthServices.Authorization) | Authorization Services. Use Keycloak as authorization server |
| `Keycloak.AuthServices.Sdk` | [![Nuget](https://img.shields.io/nuget/v/Keycloak.AuthServices.Sdk.svg)](https://nuget.org/packages/Keycloak.AuthServices.Sdk) | HTTP API integration with Keycloak |
| `Keycloak.AuthServices.Sdk.Kiota` | [![Nuget](https://img.shields.io/nuget/v/Keycloak.AuthServices.Sdk.Kiota.svg)](https://nuget.org/packages/Keycloak.AuthServices.Sdk.Kiota) | HTTP API integration with Keycloak based on OpenAPI |
| `Keycloak.AuthServices.OpenTelemetry` | [![Nuget](https://img.shields.io/nuget/v/Keycloak.AuthServices.OpenTelemetry.svg)](https://nuget.org/packages/Keycloak.AuthServices.OpenTelemetry) | OpenTelemetry support |
| `Keycloak.AuthServices.Templates` | [![Nuget](https://img.shields.io/nuget/v/Keycloak.AuthServices.Templates.svg)](https://nuget.org/packages/Keycloak.AuthServices.Templates) | `dotnet new` templates |


[![GitHub Actions Build History](https://buildstats.info/github/chart/nikiforovall/keycloak-authorization-services-dotnet?branch=main&includeBuildsFromPullRequest=false)](https://github.com/NikiforovAll/keycloak-authorization-services-dotnet/actions)

Expand Down
12 changes: 11 additions & 1 deletion docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -105,22 +105,32 @@ export default withMermaid({
},
{
text: 'Maintenance👨‍🔬',
collapsed: true,
items: [
{ text: 'Q&A', link: '/qa/recipes' },
{ text: 'Troubleshooting', link: '/qa/troubleshooting' },
{ text: 'OpenTelemetry🔭', link: '/opentelemetry' }
]
},
{
text: 'Dev Experience 🛠️',
collapsed: true,
items: [
{ text: 'Templates', link: '/devex/templates' },
{ text: 'Aspire Support', link: '/devex/aspire' },
]
},
{
text: 'Examples',
collapsed: false,
items: [
{ text: 'Web API Getting Started', link: '/examples/auth-getting-started' },
{ text: 'Aspire + Web API', link: '/examples/aspire-web-api' },
{ text: 'Authorization', link: '/examples/authorization-getting-started' },
{ text: 'Resource Authorization ✨', link: '/examples/resource-authorization' },
{ text: 'Clean Architecture', link: '/examples/auth-clean-arch' },
{ text: 'Web App MVC', link: '/examples/web-app-mvc' },
{ text: 'Web API + Blazor', link: '/examples/web-api-blazor' }
{ text: 'Web API + Blazor', link: '/examples/web-api-blazor' },
]
}
]
Expand Down
122 changes: 122 additions & 0 deletions docs/devex/aspire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Aspire Support

[.NET Aspire](https://learn.microsoft.com/en-us/dotnet/aspire/get-started/aspire-overview) is an opinionated, cloud ready stack for building observable, production ready, distributed applications.

`Keycloak.AuthServices.Aspire.Hosting` adds a support to run Keycloak Instance as a component. It is intended to be used together with `Keycloak.AuthServices`.

See working example here. [Examples/Aspire + Web API](/examples/aspire-web-api)

## Add to existing application

Install [Keycloak.AuthServices.Aspire.Hosting](https://www.nuget.org/packages/Keycloak.AuthServices.Aspire.Hosting) package to "AppHost":

```bash
dotnet add package Keycloak.AuthServices.Aspire.Hosting
```

Modify the `AppHost/Program.cs`:

```csharp
// AppHost/Program.cs
var builder = DistributedApplication.CreateBuilder(args);

var keycloak = builder // [!code focus]
.AddKeycloakContainer("keycloak"); // [!code focus]
var realm = keycloak.AddRealm("Test"); // [!code focus]
builder.AddProject<Projects.Api>("api").WithReference(keycloak).WithReference(realm); // [!code focus]
builder.Build().Run();
```

Here is what it does:

1. Starts the instance of Keycloak as docker container.
2. `WithReference(keycloak)` adds Keycloak server instance to Service Discovery.
3. `WithReference(realm)` adds `Keycloak__Realm` and `Keycloak__AuthServerUrl` environment variables.

From this point you are almost finished, the only this is left is to configure Authentication missing parts:

```csharp
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
var configuration = builder.Configuration;

builder.AddServiceDefaults();

services.AddKeycloakWebApiAuthentication( // [!code focus]
configuration, // [!code focus]
options => // [!code focus]
{ // [!code focus]
options.Audience = "workspaces-client"; // [!code focus]
options.RequireHttpsMetadata = false; // [!code focus]
} // [!code focus]
); // [!code focus]
services.AddAuthorization(); // [!code focus]
var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthentication();
app.UseAuthorization();

app.MapGet("/hello", () => "Hello World!").RequireAuthorization();

app.Run();

```

Run:

```bash
dotnet run --project ./AppHost
```

### Import configuration files

You can reference import files and bind Keycloak data volumes to persist Keycloak configuration and share it with others.

```csharp
var builder = DistributedApplication.CreateBuilder(args);

var keycloak = builder
.AddKeycloakContainer("keycloak")
.WithDataVolume()
.WithImport("./KeycloakConfiguration/Test-realm.json")
.WithImport("./KeycloakConfiguration/Test-users-0.json");

var realm = keycloak.AddRealm("Test");

builder.AddProject<Projects.Api>("api").WithReference(keycloak).WithReference(realm);

builder.Build().Run();
```

> [!TIP]
> You can sync your current configuration via CLI
Inside docker container run:

```bash
/opt/keycloak/bin/kc.sh export --dir /opt/keycloak/data/import --realm Test
```

## Start from Template

You can use [Keycloak.AuthServices.Templates](https://www.nuget.org/packages/Keycloak.AuthServices.Templates) to scaffold the new Aspire Project that has `Keycloak.AuthServices` integration configured.

Install template:

```bash
dotnet new install Keycloak.AuthServices.Templates
```

Scaffold a solution:

```bash
dotnet new keycloak-aspire-starter -o $dev/KeycloakAspireStarter --EnableKeycloakImport
```

See [Aspire Template](/devex/templates#aspire-web-api) for more details.
143 changes: 143 additions & 0 deletions docs/devex/templates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Templates

You can use [dotnet-new](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-new) command to scaffold various solution items. `Keyclaok.AuthServices` provides you with templates to get started with Keycloak and .NET

## Getting Started

Install:

```bash
dotnet new install Keycloak.AuthServices.Templates
```

List installed packages:

```bash
❯ dotnet new list keycloak
# These templates matched your input: 'keycloak'

# Template Name Short Name Language Tags
# ----------------------- ----------------------- -------- -------------------------------------
# Keycloak Aspire Starter keycloak-aspire-starter [C#] Common/.NET Aspire/Cloud/API/Keycloak
# Keycloak WebApi keycloak-webapi [C#] Common/API/Keycloak
```

> [!NOTE]
> To successfully run commands below - replace `$dev` with the actual working directory.
>
> You can set shell variable by running next command: `export dev=~/projects`
## Web API

Use `dotnet new keycloak-webapi -h` to discover how to use this template.

### Example

Verify the output of the command by using `--dry-run` option:

```bash
❯ dotnet new keycloak-webapi -o $dev/KeycloakWebApi --dry-run
# File actions would have been taken:
# Create: $dev\KeycloakWebApi\Directory.Build.props
# Create: $dev\KeycloakWebApi\Directory.Packages.props
# Create: $dev\KeycloakWebApi\Extensions.OpenApi.cs
# Create: $dev\KeycloakWebApi\KeycloakWebApi.csproj
# Create: $dev\KeycloakWebApi\Program.cs
# Create: $dev\KeycloakWebApi\Properties\launchSettings.json
# Create: $dev\KeycloakWebApi\README.md
# Create: $dev\KeycloakWebApi\appsettings.Development.json
# Create: $dev\KeycloakWebApi\appsettings.json
```

Generate:

```bash
❯ dotnet new keycloak-webapi -o $dev/KeycloakWebApi
# The template "Keycloak WebApi" was created successfully.
```

Run:

```bash
❯ dotnet run --project $dev/KeycloakWebApi
# Building...
# info: Microsoft.Hosting.Lifetime[14]
# Now listening on: https://localhost:7107
# info: Microsoft.Hosting.Lifetime[14]
# Now listening on: http://localhost:5064
# info: Microsoft.Hosting.Lifetime[0]
# Application started. Press Ctrl+C to shut down.
# info: Microsoft.Hosting.Lifetime[0]
# Hosting environment: Development
# info: Microsoft.Hosting.Lifetime[0]
# Content root path: $dev\KeycloakWebApi
```

⚠️To finish the configuration process you will need to:

1. Start Keycloak instance, see the generated `README.md` file.
2. Create a *Realm*
3. Register a *Client*
4. Update `appsettings.Development.json` correspondingly.

## Aspire + Web API

Use `dotnet new keycloak-aspire-starter -h` to discover how to use this template.

### Example

Verify the output of the command by using `--dry-run` option:

```bash
❯ dotnet new keycloak-aspire-starter -o $dev/KeycloakAspireStarter --EnableKeycloakImport --dry-run
# File actions would have been taken:
# Create: $dev\KeycloakAspireStarter\.gitignore
# Create: $dev\KeycloakAspireStarter\Api\Api.csproj
# Create: $dev\KeycloakAspireStarter\Api\Extensions.OpenApi.cs
# Create: $dev\KeycloakAspireStarter\Api\Program.cs
# Create: $dev\KeycloakAspireStarter\Api\Properties\launchSettings.json
# Create: $dev\KeycloakAspireStarter\Api\appsettings.Development.json
# Create: $dev\KeycloakAspireStarter\Api\appsettings.json
# Create: $dev\KeycloakAspireStarter\AppHost\AppHost.csproj
# Create: $dev\KeycloakAspireStarter\AppHost\KeycloakConfiguration\Test-realm.json
# Create: $dev\KeycloakAspireStarter\AppHost\KeycloakConfiguration\Test-users-0.json
# Create: $dev\KeycloakAspireStarter\AppHost\Program.cs
# Create: $dev\KeycloakAspireStarter\AppHost\Properties\launchSettings.json
# Create: $dev\KeycloakAspireStarter\AppHost\appsettings.Development.json
# Create: $dev\KeycloakAspireStarter\AppHost\appsettings.json
# Create: $dev\KeycloakAspireStarter\Directory.Build.props
# Create: $dev\KeycloakAspireStarter\Directory.Packages.props
# Create: $dev\KeycloakAspireStarter\KeycloakAspireStarter.sln
# Create: $dev\KeycloakAspireStarter\README.md
# Create: $dev\KeycloakAspireStarter\ServiceDefaults\Extensions.cs
# Create: $dev\KeycloakAspireStarter\ServiceDefaults\ServiceDefaults.csproj
# Create: $dev\KeycloakAspireStarter\global.json
```

Generate:

```bash
❯ dotnet new keycloak-aspire-starter -o $dev/KeycloakAspireStarter --EnableKeycloakImport
# The template "Keycloak Aspire Starter" was created successfully.
```

We want to enable automatically imported realm by adding `--EnableKeycloakImport` option.

Run:

```bash
❯ dotnet run --project $dev/KeycloakAspireStarter/AppHost/
# Building...
# info: Aspire.Hosting.DistributedApplication[0]
# Aspire version: 8.0.1+a6e341ebbf956bbcec0dda304109815fcbae70c9
# info: Aspire.Hosting.DistributedApplication[0]
# Distributed application starting.
# info: Aspire.Hosting.DistributedApplication[0]
# Application host directory is: $dev\KeycloakAspireStarter\AppHost
# info: Aspire.Hosting.DistributedApplication[0]
# Now listening on: http://localhost:15056
# info: Aspire.Hosting.DistributedApplication[0]
# Distributed application started. Press Ctrl+C to shut down.
```

Additionally, open Keycloak master realm by navigating: <http://localhost:8080/>. Use `admin:admin` as credentials.
28 changes: 28 additions & 0 deletions docs/examples/aspire-web-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Aspire + Web API

This samples contains Keycloak installation configured via configuration files.

Here is what it does:

1. Starts a Keycloak Instance as part of Aspire Integration
2. Imports realm and test users (`test1:test`, `test2:test`)

The Keycloak is already configured, all you need to do is to run sample and try to retrieve token via Swagger UI.

Run:

```bash
dotnet run --project ./AppHost
```

## Code

`AppHost`:

<<< @/../samples/GettingStartedAndAspire/AppHost/Program.cs

`Api`:

<<< @/../samples/GettingStartedAndAspire/Api/Program.cs

See sample source code: [keycloak-authorization-services-dotnet/tree/main/samples/GettingStartedAndAspire](https://github.com/NikiforovAll/keycloak-authorization-services-dotnet/tree/main/samples/GettingStartedAndAspire)
Loading

0 comments on commit 940edad

Please sign in to comment.