SuperTokens 1.1.0
dotnet add package SuperTokens --version 1.1.0
NuGet\Install-Package SuperTokens -Version 1.1.0
<PackageReference Include="SuperTokens" Version="1.1.0" />
<PackageVersion Include="SuperTokens" Version="1.1.0" />
<PackageReference Include="SuperTokens" />
paket add SuperTokens --version 1.1.0
#r "nuget: SuperTokens, 1.1.0"
#:package SuperTokens@1.1.0
#addin nuget:?package=SuperTokens&version=1.1.0
#tool nuget:?package=SuperTokens&version=1.1.0
SuperTokens .NET SDK (port of supertokens-golang)
A C# / .NET port of the SuperTokens Go backend SDK.
Targets .NET 10, idiomatic C# (async/await, nullable reference types, exceptions
instead of (value, error) tuples), System.Text.Json, and ASP.NET Core
minimal-API / middleware integration (no MVC controllers).
Solution layout
SuperTokens.sln
Directory.Build.props # shared: net10.0, nullable, implicit usings
CONVENTIONS.md # Go → C# porting contract
src/SuperTokens/ # the SDK (one class library, one assembly)
Core/ # namespace SuperTokens — framework core
Exceptions, Constants, Logger, Json, UserContext, Models,
NormalisedUrlDomain, NormalisedUrlPath, Utils, Querier,
RecipeModule, PostInitCallbacks, UserIdMapping,
SuperTokensInstance, SuperTokensApi
Ingredients/
EmailDelivery/ # SuperTokens.Ingredients.EmailDelivery (MailKit/SMTP)
SmsDelivery/ # SuperTokens.Ingredients.SmsDelivery (Twilio)
Recipe/
Jwt, OpenId, Session, Multitenancy, ThirdParty, EmailVerification,
UserRoles, EmailPassword, Passwordless, UserMetadata, Dashboard
test/SuperTokens.Tests/ # xUnit tests
Unlike Go (where each recipe is a separate package and import cycles are illegal),
the entire SDK is one assembly, so recipes may reference each other freely.
Namespaces still mirror the Go package tree (SuperTokens.Recipe.<Name>).
Design decisions
map[string]interface{}→Dictionary<string, object?>. A customInferredTypesConverter(inCore/Json.cs) makes System.Text.Json deserialize into native CLR types (double / bool / string / Dictionary / List), matching Go'sencoding/jsonsemantics.- Errors → exceptions.
SuperTokensException(base) andBadInputException(→ HTTP 400). Functions that returned(T, error)now returnTand throw. - IO is async. The querier uses
HttpClient; everything reaching the core isasync Task. - Overridable interfaces. Go's "struct of function pointers + Override" pattern is ported as sealed classes with nullable delegate properties.
- Web layer. Go's
net/httpmiddleware/handler model maps to ASP.NET CoreHttpContext+RequestDelegate. Register withapp.UseSuperTokens().
Usage sketch (minimal API)
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
await SuperTokensApi.Init(new SuperTokensConfig
{
Supertokens = new ConnectionInfo { ConnectionUri = "http://localhost:3567" },
AppInfo = new AppInfo
{
AppName = "My App",
ApiDomain = "http://localhost:3001",
WebsiteDomain = "http://localhost:3000",
},
RecipeList = new List<RecipeFactory>
{
// Session.Init(), EmailPassword.Init(), ...
},
});
app.UseSuperTokens(); // dispatches SuperTokens recipe APIs
app.MapGet("/hello", () => "hi"); // your own minimal-API endpoints
app.Run();
Protecting your own endpoints
Don't hand-roll auth checks — wrap an endpoint (or a whole route group) with
RequireSession(). It verifies the access token, and on a missing / expired /
invalid session it writes the correct response itself (a 401, or a
"try refresh token" 401 that tells the frontend to call the refresh endpoint)
and skips your handler. Inside a protected handler, read the verified session
with Session.GetSessionFromRequestContext(ctx).
using SuperTokens.AspNetCore; // RequireSession()
using SuperTokens.Recipe.Session; // Session, VerifySessionOptions
using SuperTokens.Recipe.Session.SessModels;
app.UseSuperTokens(); // must come before your endpoints
// Protected — runs only with a valid session
app.MapGet("/user/profile", (HttpContext ctx) =>
{
var session = Session.GetSessionFromRequestContext(ctx)!;
return Results.Ok(new
{
userId = session.GetUserId(),
payload = session.GetAccessTokenPayload(),
});
}).RequireSession();
// Optional session — handler still runs when there's no session
app.MapGet("/maybe", (HttpContext ctx) =>
{
var session = Session.GetSessionFromRequestContext(ctx); // null if not signed in
return Results.Ok(new { signedIn = session is not null });
}).RequireSession(new VerifySessionOptions { SessionRequired = false });
// Protect a whole group
var secure = app.MapGroup("/api").RequireSession();
secure.MapGet("/orders", (HttpContext ctx) => Results.Ok(/* ... */));
Notes:
app.UseSuperTokens()must be registered before your endpoints (it wires the/auth/*recipe routes and the shared error handler that turns session failures into proper responses).- For role/permission checks, pass claim validators via
VerifySessionOptions.OverrideGlobalClaimValidators, or readsession.GetAccessTokenPayload()and check it yourself. - Avoid calling
Session.GetSession(req, res)directly in a custom endpoint unless you wrap it: withSessionRequireddefaulting to true it throwsUnauthorisedError/TryRefreshTokenError, and those only become proper HTTP responses when routed through the SuperTokens error handler — whichRequireSession()does for you.
Status
| Layer | State |
|---|---|
Core framework (Core/) |
✅ ported & building; URL-normalisation + version tests pass |
| Ingredients (email/SMS) | ✅ ported & building |
| Recipes | ✅ all 11 ported & building |
| Integration tests | ✅ 22 tests across 8 recipes verified against a live supertokens-core + Postgres |
Known limitations / gaps
- Public Suffix List.
Utils.GetTopLevelDomainForSameSiteResolutionuses a last-two-labels heuristic instead of the PSL that Go uses (golang.org/x/net/publicsuffix). Multi-level suffixes (e.g..co.uk) are not resolved precisely. Consider bundling a PSL library for production. - Querier GET cache. The per-request core-call cache keyed by params+headers is simplified; cache invalidation hooks exist but the GET read-through cache is not fully reproduced.
- See per-recipe notes for recipe-specific gaps.
Running the tests
# Fast unit tests (no Docker)
dotnet test test/SuperTokens.Tests
# Integration tests — require Docker. Testcontainers starts a real
# supertokens-core backed by a real PostgreSQL (both on a shared network) and the
# SDK runs emailpassword sign-up/sign-in and session create/verify/revoke against it.
dotnet test test/SuperTokens.IntegrationTests
Images (both overridable via env vars):
| Container | Default | Override |
|---|---|---|
| Core | supertokens/supertokens-postgresql:10.0.0 |
SUPERTOKENS_CORE_IMAGE |
| Database | postgres:16-alpine |
SUPERTOKENS_POSTGRES_IMAGE |
The core is pinned to a version that advertises CDI 3.1 (the version this SDK targets). Testcontainers' Ryuk cleans up both containers and the network automatically.
Reference source
The original Go source is cloned (git-ignored) at _src_golang/ for reference
during the port.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- libphonenumber-csharp (>= 9.0.2)
- MailKit (>= 4.9.0)
- Microsoft.IdentityModel.Tokens (>= 8.3.0)
- System.IdentityModel.Tokens.Jwt (>= 8.3.0)
- Twilio (>= 7.8.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.