Skip to content

Commit

Permalink
Add Kratos sections
Browse files Browse the repository at this point in the history
  • Loading branch information
Wojciech Klusek committed Jan 5, 2024
1 parent 96ffeeb commit 43b8aca
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 1 deletion.
2 changes: 1 addition & 1 deletion docs/cqrs/authorization/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. <!-- TODO: add link to Ory Kratos page -->
> **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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<KratosIdentitySyncHandler>()
.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<ErrorMessage>
{
new ErrorMessage(
null,
new List<DetailedMessage>
{
new DetailedMessage(
1,
"identity is null",
"error",
null),
}),
},
422
);
return;
}
else if (identity.Id == default)
{
await WriteErrorResponseAsync(
ctx,
new List<ErrorMessage>
{
new ErrorMessage(
null,
new List<DetailedMessage>
{
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<KratosIdentityUpdated>
{
private readonly CoreDbContext dbContext;

public SyncKratosIdentity(CoreDbContext dbContext)
{
this.dbContext = dbContext;
}

public async Task Consume(ConsumeContext<KratosIdentityUpdated> 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
76 changes: 76 additions & 0 deletions docs/external_integrations/authorization_ory_kratos/index.md
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit 43b8aca

Please sign in to comment.