-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Wojciech Klusek
committed
Jan 5, 2024
1 parent
96ffeeb
commit 43b8aca
Showing
4 changed files
with
257 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
177 changes: 177 additions & 0 deletions
177
docs/external_integrations/authorization_ory_kratos/handling_webhooks.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
76
docs/external_integrations/authorization_ory_kratos/index.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters