From 43b8acade765a08c397244df21fde5c94136660a Mon Sep 17 00:00:00 2001 From: Wojciech Klusek Date: Thu, 26 Oct 2023 09:23:17 +0200 Subject: [PATCH] Add Kratos sections --- docs/cqrs/authorization/index.md | 2 +- .../handling_webhooks.md | 177 ++++++++++++++++++ .../authorization_ory_kratos/index.md | 76 ++++++++ mkdocs.yml | 3 + 4 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 docs/external_integrations/authorization_ory_kratos/handling_webhooks.md create mode 100644 docs/external_integrations/authorization_ory_kratos/index.md diff --git a/docs/cqrs/authorization/index.md b/docs/cqrs/authorization/index.md index 4109bd99d..37364c169 100644 --- a/docs/cqrs/authorization/index.md +++ b/docs/cqrs/authorization/index.md @@ -203,7 +203,7 @@ All [queries], [commands] and [operations] can (and should!) be behind authoriza } ``` -> **Tip:** You can implement your own authorization and use it with LeanCode CoreLibrary authorizers. To see how you can implement authorization using Ory Kratos and LeanCode CoreLibrary see here. +> **Tip:** You can implement your own authorization and use it with LeanCode CoreLibrary authorizers. To see how you can implement authorization using Ory Kratos and LeanCode CoreLibrary see [here](../../external_integrations/authorization_ory_kratos/index.md). [query]: ../query/index.md [command]: ../command/index.md diff --git a/docs/external_integrations/authorization_ory_kratos/handling_webhooks.md b/docs/external_integrations/authorization_ory_kratos/handling_webhooks.md new file mode 100644 index 000000000..2d601172b --- /dev/null +++ b/docs/external_integrations/authorization_ory_kratos/handling_webhooks.md @@ -0,0 +1,177 @@ +# Handling webhooks + +To effectively manage incoming webhooks from Ory Kratos, you can employ the [KratosWebHookHandlerBase] class. The following example demonstrates how to synchronize identity data received from Kratos through webhooks with your database using [MassTransit]. This assumes that your Kratos instance is properly configured to send webhooks to your application. + +## Mapping the Endpoint in Application Configuration + +First, you should map the `KratosIdentitySyncHandler` class to the `/kratos/sync-identity` endpoint in your application's configuration: + +```csharp +protected override void ConfigureApp(IApplicationBuilder app) +{ + . . . + + app.UseAuthentication() + .UseEndpoints(endpoints => + { + . . . + + endpoints.MapPost( + "/kratos/sync-identity", + ctx => ctx.RequestServices + .GetRequiredService() + .HandleAsync(ctx) + ); + + . . . + }); + + . . . +} +``` + +## Defining the Webhook Handler + +Then, you can define the webhook handler to handle incoming requests. In this example, we deserialize request into [Identity] class, verify it inside a webhook and send an event that the identity has been updated, which will be handled by the appropriate `IConsumer` using [MassTransit]: + +```csharp +public partial class KratosIdentitySyncHandler : KratosWebHookHandlerBase +{ + private readonly IBus bus; + + public KratosIdentitySyncHandler( + KratosWebHookHandlerConfig config, + IBus bus) + : base(config) + { + this.bus = bus; + } + + protected override async Task HandleCoreAsync(HttpContext ctx) + { + // Deserialize request into `Identity` class + // from `LeanCode.Kratos` package + var body = await JsonSerializer.DeserializeAsync( + ctx.Request.Body, + KratosIdentitySyncHandlerContext.Default.RequestBody, + ctx.RequestAborted + ); + + var identity = body.Identity; + + if (identity is null) + { + // Helper method defined in KratosWebHookHandlerBase + await WriteErrorResponseAsync( + ctx, + new List + { + new ErrorMessage( + null, + new List + { + new DetailedMessage( + 1, + "identity is null", + "error", + null), + }), + }, + 422 + ); + return; + } + else if (identity.Id == default) + { + await WriteErrorResponseAsync( + ctx, + new List + { + new ErrorMessage( + null, + new List + { + new DetailedMessage( + 2, + "identity.id is empty", + "error", + null), + }), + }, + 422 + ); + return; + } + + // Send a message via MassTransit that identity has been updated + await bus.Publish( + new KratosIdentityUpdated(Guid.NewGuid(), Time.UtcNow, identity), + ctx.RequestAborted); + + ctx.Response.StatusCode = 200; + } + + public record struct RequestBody( + [property: JsonPropertyName("identity")] Identity? Identity); + + [JsonSourceGenerationOptions( + PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower)] + [JsonSerializable(typeof(RequestBody))] + private partial class KratosIdentitySyncHandlerContext + : JsonSerializerContext { } +} +``` + +> **Tip:** To read about LeanCode CoreLibrary MassTranist integration visit [here](../messaging_masstransit/index.md). + +## Updating the Database + +With the `KratosIdentitySyncHandler` in place, you can process incoming Kratos webhooks and handle them appropriately. The following code snippet updates the database table that stores identities (assuming that `KratosIdentities` table is already present in your database) when a `KratosIdentityUpdated` event is received: + +```csharp +public sealed record class KratosIdentityUpdated( + Guid Id, + DateTime DateOccurred, + Identity Identity) + : IDomainEvent; + +public class SyncKratosIdentity : IConsumer +{ + private readonly CoreDbContext dbContext; + + public SyncKratosIdentity(CoreDbContext dbContext) + { + this.dbContext = dbContext; + } + + public async Task Consume(ConsumeContext context) + { + var kratosIdentity = context.Message.Identity; + var identityId = kratosIdentity.Id; + + var dbIdentity = await dbContext.KratosIdentities.FindAsync( + keyValues: new[] { (object)identityId }, + context.CancellationToken + ); + + // Database transaction will be commited at the end of the pipeline + // assuming `CommitDatabaseTransactionMiddleware` was added + if (dbIdentity is null) + { + dbIdentity = new(kratosIdentity); + dbContext.KratosIdentities.Add(dbIdentity); + } + else + { + dbIdentity.Update(kratosIdentity); + dbContext.KratosIdentities.Update(dbIdentity); + } + } +} +``` + +> **Tip:** To read about handling events visit [here](../messaging_masstransit/handling_events.md) and to read about the pipeline visit [here](../../cqrs/pipeline/index.md) + +[MassTransit]: https://masstransit-project.com/ +[KratosWebHookHandlerBase]: https://github.com/leancodepl/corelibrary/blob/v8.0-preview/src/Infrastructure/LeanCode.Kratos/KratosWebHookHandlerBase.cs +[Identity]: https://github.com/leancodepl/corelibrary/blob/v8.0-preview/src/Infrastructure/LeanCode.Kratos/Model/Identity.cs diff --git a/docs/external_integrations/authorization_ory_kratos/index.md b/docs/external_integrations/authorization_ory_kratos/index.md new file mode 100644 index 000000000..e3ce946db --- /dev/null +++ b/docs/external_integrations/authorization_ory_kratos/index.md @@ -0,0 +1,76 @@ +# Authorization - Ory Kratos + +Ory Kratos is an identity and user management system built for cloud-based environments. It focuses on security while offering developer-friendly tools. This open-source project provides a flexible platform for developers to customize and integrate authentication workflows, ensuring a seamless user experience in the cloud. For more information, you can explore the official [Ory Kratos GitHub repository](https://github.com/ory/kratos). + +## Why Ory Kratos? + +The main thing that sets Kratos apart from all the other identity management solutions is that it is API-only, it is maintained according to current DevOps standards and has declarative configuration. + +Ory Kratos has generic email+password flow (with optional verification), there are social logins, and WebAuthN. It also standardizes all the accompanying flows, like registration, email/password change, password reset, email verification, MFA, and alike. For a more in-depth exploration of why Kratos is our preferred choice, you can refer to this [article](https://leancode.co/blog/identity-management-solutions-part-2-the-choice). + +## LeanCode CoreLibrary integration + +LeanCode CoreLibrary provides 3 main components to integrate with Kratos: + +1. [KratosAuthenticationHandler] - converts Kratos cookie into claims. +2. [KratosWebHookHandlerBase] - provides functionality to handle Kratos webhooks and helps with serialization and deserialization of messages from/to Kratos. +3. `IServiceCollection` extension methods - which allow to register [KratosAuthenticationHandler] and Kratos `IFrontendApi`, `IIdentityApi` API clients. + +Ory Kratos can be either hosted on [Ory Network](https://www.ory.sh/network/) or be self-hosted. + +> **Tip:** To see quickstart about how you can run self-hosted Kratos instance visit [here](https://www.ory.sh/docs/kratos/quickstart). + +## Configuration + +To integrate Kratos into LeanCode CoreLibrary-based application, you can follow the example below. This example demonstrates the use of the `AddKratos(...)` method to register the [KratosAuthenticationHandler]. It also adds the `user` role to every identity and assigns the `admin` role to every identity with an email in the `@leancode.pl` domain. Additionally, this example registers the `IFrontendApi` and `IIdentityApi` classes for interaction with the Kratos API: + +```csharp +public override void ConfigureServices(IServiceCollection services) +{ + . . . + services + .AddAuthentication() + .AddKratos(options => + { + options.NameClaimType = KnownClaims.UserId; + options.RoleClaimType = KnownClaims.Role; + + options.ClaimsExtractor = (s, o, c) => + { + // Every identity is a valid User + c.Add(new(o.RoleClaimType, "user")); + + if ( + s.Identity.VerifiableAddresses.Any( + kvia => + kvia.Via == "email" + && kvia.Value.EndsWith( + "@leancode.pl", + false, + CultureInfo.InvariantCulture) + && kvia.Verified + ) + ) + { + c.Add(new(o.RoleClaimType, "admin")); + } + }; + }); + + services.AddKratosClients(builder => + { + // Kratos public endpoint + builder.AddFrontendApiClient(""); + + // Kratos admin endpoint + builder.AddIdentityApiClient(""); + }); + + // Api key which will be send by Kratos + services.AddSingleton(new LeanCode.Kratos.KratosWebHookHandlerConfig("")); + . . . +} +``` + +[KratosAuthenticationHandler]: https://github.com/leancodepl/corelibrary/blob/v8.0-preview/src/Infrastructure/LeanCode.Kratos/KratosAuthenticationHandler.cs +[KratosWebHookHandlerBase]: https://github.com/leancodepl/corelibrary/blob/v8.0-preview/src/Infrastructure/LeanCode.Kratos/KratosWebHookHandlerBase.cs diff --git a/mkdocs.yml b/mkdocs.yml index b72e13ea4..51619ffc6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -51,6 +51,9 @@ nav: - Messaging - MassTransit: - ./external_integrations/messaging_masstransit/index.md - Handling events: ./external_integrations/messaging_masstransit/handling_events.md + - Authorization - Ory Kratos: + - ./external_integrations/authorization_ory_kratos/index.md + - Handling webhooks: ./external_integrations/authorization_ory_kratos/handling_webhooks.md - Features: - ./features/index.md - Force update: