The WorkOS library for .NET provides convenient access to the WorkOS API from applications using .NET. Supports .NET 8+
See the API Reference for .NET usage examples.
There are several options to install the WorkOS .NET SDK.
nuget install WorkOS.netdotnet add package WorkOS.netInstall-Package WorkOS.netTo use the WorkOS client, you must provide an API key from the WorkOS dashboard.
WorkOSConfiguration.SetApiKey("sk_key123");You can also optionally provide a custom HttpClient.
var client = new WorkOSClient(
new WorkOSOptions
{
ApiKey = "sk_key123",
HttpClient = ...,
});
WorkOSConfiguration.WorkOSClient = client;SSO and User Management endpoints that accept a client_id parameter (for example,
UserManagement.AuthenticateWithPassword and SSO.GetAuthorizationUrl) require the
WorkOS Client ID. Provide it via WorkOSOptions.ClientId:
var client = new WorkOSClient(
new WorkOSOptions
{
ApiKey = "sk_key123",
ClientId = "client_01H...",
});
WorkOSConfiguration.WorkOSClient = client;Operations that require a Client ID throw InvalidOperationException if one was not
configured, instead of silently sending an empty string to the API.
Most authentication flows start by redirecting the end-user to a WorkOS-hosted
URL that the SDK builds for you. WorkOSClient exposes URL builders for
AuthKit, SSO, and logout. These helpers build URLs locally and do not make a
network call.
var url = WorkOSConfiguration.WorkOSClient.UserManagement.GetAuthorizationUrl(
new UserManagementGetAuthorizationUrlOptions
{
RedirectUri = "https://example.com/callback",
Provider = UserManagementAuthenticationProvider.Authkit,
State = csrfToken,
});
return Redirect(url);If you are building a browser, mobile, CLI, or desktop app that should not
hold an API key, use PublicWorkOSClient instead. It accepts a client_id
only and exposes only public-safe URL builders:
var publicClient = new PublicWorkOSClient(
new PublicWorkOSOptions { ClientId = "client_01H..." });
var url = publicClient.GetAuthorizationUrl(
new AuthKitAuthorizationUrlOptions
{
RedirectUri = "myapp://callback",
State = csrfToken,
});For any client that cannot safely hold an API key, use PKCE. The SDK generates
and returns the code_verifier alongside the URL. Store that verifier in your
app's session so you can pass it back on the token exchange step.
var result = publicClient.GetAuthorizationUrlWithPkce(
new AuthKitAuthorizationUrlOptions
{
RedirectUri = "myapp://callback",
});
HttpContext.Session.SetString("pkce_verifier", result.CodeVerifier);
return Redirect(result.Url);For integrations where you already know the connection, organization, or provider, use the SSO builder:
var url = WorkOSConfiguration.WorkOSClient.SSO.GetAuthorizationUrl(
new SSOGetAuthorizationUrlOptions
{
RedirectUri = "https://example.com/callback",
Connection = "conn_01H...",
State = csrfToken,
});PublicWorkOSClient also exposes GetSSOAuthorizationUrl and
GetSSOAuthorizationUrlWithPkce for public clients.
After the user returns to your redirect URI, exchange the code for a user
and session:
var authResponse = await WorkOSConfiguration.WorkOSClient.UserManagement
.AuthenticateWithCodeAsync(
new AuthenticateWithCodeOptions
{
Code = Request.Query["code"],
// If you used PKCE, include the verifier you saved earlier.
CodeVerifier = HttpContext.Session.GetString("pkce_verifier"),
});
var user = authResponse.User;Ending a WorkOS session can also require signing the user out of the upstream
identity provider. Use GetLogoutUrl to build the redirect URL, then send the
browser to it:
var logoutUrl = WorkOSConfiguration.WorkOSClient.UserManagement.GetLogoutUrl(
new UserManagementGetLogoutUrlOptions
{
SessionId = sessionId,
ReturnTo = "https://example.com/",
});
Response.Cookies.Delete("wos_session");
return Redirect(logoutUrl);If you manage sessions outside AuthKit, the same helper is available on
PublicWorkOSClient:
var logoutUrl = publicClient.GetLogoutUrl(sessionId, returnTo: "https://example.com/");The SDK ships JSON metadata for both Newtonsoft.Json
(used by the runtime to talk to the WorkOS API) and
System.Text.Json
(STJ). All generated DTOs, enums, AnyOf<T...> values, and webhook envelopes
work under either serializer:
// Both round-trip the same payload.
var newtonsoft = JsonConvert.DeserializeObject<Organization>(json);
var stj = System.Text.Json.JsonSerializer.Deserialize<Organization>(json);Enum forward compatibility is identical on both stacks: an enum value the SDK
hasn't seen before deserializes to the type's Unknown member instead of
throwing.
Most services on WorkOSClient are generated from the WorkOS OpenAPI
specification (Organizations, UserManagement, DirectorySync, SSO,
AuditLogs, Events, Webhooks, etc.). In addition, the SDK ships a handful
of hand-maintained services that do not correspond to a single REST
endpoint:
| Service | Purpose |
|---|---|
client.Passwordless |
Magic-link / passwordless session helpers. |
client.Vault |
Key-value storage and envelope encryption helpers. |
client.Actions |
AuthKit Actions signing-secret verification and payload signing. |
client.Session |
Sealed-session management, cookie helpers, and JWT validation. |
These services are fully supported and are accessed the same way as generated services:
var payload = client.Actions.VerifyAndParse(rawBody, signatureHeader);
var session = client.Session.LoadSealedSession(cookieValue);Every REST endpoint in the WorkOS OpenAPI spec has a typed wrapper on one of
the services under WorkOSClient, and you should usually prefer those.
When you need to call an endpoint the SDK does not yet expose, you can drop
down to WorkOSClient.MakeAPIRequest<T> or WorkOSClient.MakeRawAPIRequest.
Both methods take a WorkOSRequest:
| Property | Purpose |
|---|---|
Method |
HttpMethod.Get, HttpMethod.Post, etc. |
Path |
Absolute path starting with /, for example /organizations. |
Options |
Any BaseOptions subclass. The SDK serializes it as query params for GET/DELETE requests or as a JSON body for mutating requests. |
AccessToken |
Optional bearer token to send instead of the API key. |
WorkOSHeaders |
Optional WorkOS-specific headers to include on the request. |
RequestOptions |
Per-call overrides such as ApiKey, IdempotencyKey, or MaxRetries. |
BaseOptions is a marker base class. For direct calls, you define your own
subclass with the fields you want to serialize.
public class CustomListOptions : BaseOptions
{
public string? OrganizationId { get; set; }
public int? Limit { get; set; }
}
var request = new WorkOSRequest
{
Method = HttpMethod.Get,
Path = "/some/new/endpoint",
Options = new CustomListOptions
{
OrganizationId = "org_01H...",
Limit = 50,
},
};
var response = await WorkOSConfiguration.WorkOSClient
.MakeAPIRequest<MyCustomResponse>(request);If you need access to headers, status code, or a non-JSON body, use the raw
variant and handle the HttpResponseMessage yourself:
using var response = await WorkOSConfiguration.WorkOSClient
.MakeRawAPIRequest(request);
response.EnsureSuccessStatusCode();
var retryAfter = response.Headers.RetryAfter;
var body = await response.Content.ReadAsStringAsync();Generated service methods and raw requests both accept RequestOptions:
var org = await WorkOSConfiguration.WorkOSClient.Organizations.CreateAsync(
new OrganizationsCreateOptions { Name = "Acme" },
new RequestOptions
{
IdempotencyKey = "acme-tenant-create-v1",
MaxRetries = 0,
});The SDK automatically adds an Idempotency-Key header to POST requests when
one is not provided explicitly.
All non-2xx responses from the WorkOS API raise subclasses of WorkOS.ApiException.
The SDK maps well-known status codes to specific exception types so you can
catch exactly what you care about:
| HTTP status | Exception type |
|---|---|
| 401 | AuthenticationException |
| 404 | NotFoundException |
| 422 | UnprocessableEntityException |
| 429 | RateLimitExceededException |
| 5xx | ServerException |
| other | ApiException |
Every exception exposes a StatusCode property and carries the raw response
body in Exception.Message.
try
{
var org = await client.Organizations.GetOrganization("org_01H...");
}
catch (NotFoundException)
{
// organization does not exist
}
catch (RateLimitExceededException)
{
// back off per Retry-After and retry
}
catch (ApiException ex)
{
Console.Error.WriteLine($"WorkOS API error {(int)ex.StatusCode}: {ex.Message}");
throw;
}The SDK automatically retries failed requests that receive a 429 (rate limit)
or 5xx (server error) response. Retries use exponential backoff with full
jitter and honor the Retry-After header when present. By default the SDK
retries up to 2 times; you can change this via WorkOSOptions.MaxRetries
or disable retries entirely by setting it to 0.
Run all tests with the following command:
dotnet test test/WorkOSTests/WorkOSTests.csprojRun tests for a specific framework with the following command:
dotnet test test/WorkOSTests/WorkOSTests.csproj -f net8.0The SDK follows the Azure SDK mocking guidelines:
- Every service class (
UserManagementService,SSOService,OrganizationsService,DirectorySyncService, etc.) exposes a parameterless constructor. - Service accessors on
WorkOSClientare declaredvirtual, and generated service methods are virtual where the SDK surface permits overriding.
That lets mocking libraries such as Moq or NSubstitute substitute the SDK without requiring custom interfaces.
using Moq;
using WorkOS;
var userManagement = new Mock<UserManagementService>();
userManagement
.Setup(s => s.GetAsync(
It.IsAny<string>(),
It.IsAny<RequestOptions>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(new User { Id = "user_123", Email = "marceline@example.com" });
var client = new Mock<WorkOSClient>(new WorkOSOptions { ApiKey = "sk_test" });
client.Setup(c => c.UserManagement).Returns(userManagement.Object);
var sut = new UserController(client.Object);Prefer constructor-injecting WorkOSClient into your own classes rather than
reaching for WorkOSConfiguration.WorkOSClient directly. That keeps mocked
clients isolated to the test under test.
For our SDKs WorkOS follows a Semantic Versioning process where all releases will have a version X.Y.Z (like 1.0.0) pattern wherein Z would be a bug fix (I.e. 1.0.1), Y would be a minor release (1.1.0) and X would be a major release (2.0.0). We permit any breaking changes to only be released in major versions and strongly recommend reading changelogs before making any major version upgrades.
WorkOS has features in Beta that can be accessed via Beta releases. We would love for you to try these and share feedback with us before these features reach general availability (GA). To install a Beta version, please follow the installation steps above using the Beta release version.
Note: there can be breaking changes between Beta versions. Therefore, we recommend pinning the package version to a specific version. This way you can install the same version each time without breaking changes unless you are intentionally looking for the latest Beta version.
We highly recommend keeping an eye on when the Beta feature you are interested in goes from Beta to stable so that you can move to using the stable version.