Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/hawxy/clerk.net
Clerk SDK for .NET/C#
https://github.com/hawxy/clerk.net
aspnetcore authentication authorization clerk sdk
Last synced: 2 days ago
JSON representation
Clerk SDK for .NET/C#
- Host: GitHub
- URL: https://github.com/hawxy/clerk.net
- Owner: Hawxy
- License: apache-2.0
- Created: 2023-10-28T13:54:23.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2024-07-18T11:01:09.000Z (4 months ago)
- Last Synced: 2024-08-10T10:57:52.741Z (3 months ago)
- Topics: aspnetcore, authentication, authorization, clerk, sdk
- Language: C#
- Homepage: https://clerk.com
- Size: 704 KB
- Stars: 45
- Watchers: 4
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Clerk SDK for .NET
[![Nuget](https://img.shields.io/nuget/v/Clerk.Net?label=Clerk.Net&style=flat-square)](https://www.nuget.org/packages/Clerk.Net)
[![Nuget](https://img.shields.io/nuget/v/Clerk.Net.DependencyInjection?label=Clerk.Net.DependencyInjection&style=flat-square)](https://www.nuget.org/packages/Clerk.Net.DependencyInjection)_Looking for ASP.NET Core w/ Clerk JWTs? [See below](#what-about-jwt-auth)._
### Packages
**`Clerk.Net`**: Provides the standalone API Client as a Kiota-generated wrapper over Clerk's OpenAPI spec. Compatible with .NET 6+ and .NET Framework 4.7.2+.
**`Clerk.Net.DependencyInjection`**: Extensions to register the `ClerkApiClient` into your DI container. Compatible with .NET 6+.
These libraries are configured as native AoT compatible for .NET 8+ consumers.
## Getting Started
Make sure to add your `SecretKey` to your application configuration, ideally via the [dotnet secrets manager](https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-6.0&tabs=windows#enable-secret-storage).
### DI Container Configuration (ASP.NET Core & Worker Services)
1. Install `Clerk.Net.DependencyInjection` from Nuget.
2. Add the following code to your service configuration:```cs
builder.Services.AddClerkApiClient(config =>
{
config.SecretKey = builder.Configuration["Clerk:SecretKey"]!;
});
```3. Request the `ClerkApiClient` in your services
```cs
public class MyBackgroundWorker : BackgroundService
{
private readonly ClerkApiClient _clerkApiClient;public MyBackgroundWorker(ClerkApiClient clerkApiClient)
{
_clerkApiClient = clerkApiClient;
}protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var invites = await _clerkApiClient.Organizations["org_abc1234"].Invitations.GetAsync(x =>
{
x.QueryParameters.Status = "pending";
});
}
}
```### Standalone Client
If you want to use the client by itself, install `Clerk.Net` and call `ClerkApiClientFactory.Create`, passing in your secret key.
The returned client should be treated as a singleton and created once for the lifetime of your application.
### HttpClient Customization
If you need to configure the underlying `HttpClient` used by the client, you can do in one of two ways:
- Configure the `IHttpClientBuilder` returned by `AddClerkApiClient`.
- Pass in a custom `HttpClient` instance to `ClerkApiClientFactory.Create`### Testing
For unit testing, see [Unit testing Kiota API clients](https://learn.microsoft.com/en-us/openapi/kiota/testing).
### What about JWT Auth?
You might have stumbled upon this repo looking for a solution for validating Clerk JWTs, so I'll include the answer here for completion. Configuring JWT auth for Clerk is a tad _unusual_ as they want you to validate the `azp` parameter instead of specifying an `audience`.
This claim isn't normally validated at the authentication layer, and so you'll need some extra code to get things working:```cs
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(x =>
{
// Authority is the URL of your clerk instance
x.Authority = builder.Configuration["Clerk:Authority"];
x.TokenValidationParameters = new TokenValidationParameters()
{
// Disable audience validation as we aren't using it
ValidateAudience = false,
NameClaimType = ClaimTypes.NameIdentifier
};
x.Events = new JwtBearerEvents()
{
// Additional validation for AZP claim
OnTokenValidated = context =>
{
var azp = context.Principal?.FindFirstValue("azp");
// AuthorizedParty is the base URL of your frontend.
if (string.IsNullOrEmpty(azp) || !azp.Equals(builder.Configuration["Clerk:AuthorizedParty"]))
context.Fail("AZP Claim is invalid or missing");return Task.CompletedTask;
}
};
});
```If you're sending requests from a SPA, it should call Clerk-JS's `getToken` as part of its HTTP middleware and append the token (prefixed with `Bearer`) to the `Authorization` header, for example:
```ts
async onRequestInit({ requestInit }) {
requestInit.headers = {
...requestInit.headers,
Authorization: `Bearer ${await getToken()}`
}
}
```If the requests are coming from an SSR environment (ie NextJS), then you can either use the session JWT via the `__session` cookie and forward it on, or use a JWT template to create a unique JWT for your scenario.
### Inbound Clerk Webhooks
If you're accepting webhooks from Clerk, you will need to validate the incoming webhook signature. To do this, you'll need to install the `Svix` package:
```bash
dotnet add package Svix
```Then, you can use the following code to validate the webhook signature:
```cs
[HttpPost("webhook")]
public async Task ClerkWebhook(IConfiguration configuration)
{
// Retrieve the Clerk webhook secret from the configuration
var clerkWebhookSecret = configuration["Clerk:WebhookSecret"];// Read the request body
var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();// Retrieve the Svix headers
var svixId = Request.Headers["svix-id"].ToString();
var svixTimestamp = Request.Headers["svix-timestamp"].ToString();
var svixSignature = Request.Headers["svix-signature"].ToString();if (string.IsNullOrEmpty(svixId) || string.IsNullOrEmpty(svixTimestamp) || string.IsNullOrEmpty(svixSignature))
{
return BadRequest("Missing headers");
}// Attempt signature verification with the raw signature
var wh = new Webhook(clerkWebhookSecret);
WebHeaderCollection headers = new WebHeaderCollection
{
{ "svix-id", svixId },
{ "svix-timestamp", svixTimestamp },
{ "svix-signature", svixSignature }
};Event webhookEvent;
try
{
wh.Verify(json, headers); // Verify doesn't return anything, just verifies the signature and throws if it's invalid
webhookEvent = JsonSerializer.Deserialize(json); // Deserialize the JSON into an Event object
}
catch (Svix.Exceptions.WebhookVerificationException ex)
{
return BadRequest("Invalid signature");
}
catch (Exception ex)
{
return BadRequest("An error occurred");
}switch (webhookEvent.Type)
{
case "user.created":
// Handle user created event
break;
case "user.updated":
// Handle user updated event
break;
case "user.deleted":
// Handle user deleted event
break;default:
return BadRequest("Unhandled event type");
}return Ok();
}// Define the Event class to represent the webhook event
public class Event
{
public string? Type { get; set; }
public ClerkUser? Data { get; set; }
}public class ClerkUser
{
public string Id { get; set; }
public string? ExternalId { get; set; }
[JsonPropertyName("first_name")]
public string? FirstName { get; set; }
[JsonPropertyName("last_name")]
public string? LastName { get; set; }
[JsonPropertyName("email_addresses")]
public List EmailAddresses { get; set; } = new List();
}public class ClerkEmailAddress
{
[JsonPropertyName("email_address")]
public string? EmailAddress { get; set; }
}
```### Disclaimer
I am not affiliated with nor represent Clerk. All support queries regarding the underlying service should go to [Clerk Support](https://clerk.com/support).