.NET quickstart
You build an ASP.NET Core Minimal API whose GET /me reads a user’s access token, checks it with the Orvano NuGet package 0.2.0, and answers with that user. You also write a command that creates a user with a server API key. This is the pattern for any backend of yours that sits next to an app signing users in with Orvano. It takes about ten minutes.
The finished app is examples/dotnet-quickstart in the Orvano repo. Every file below comes from it, and CI runs it on every change.
1. Start Orvano
Section titled “1. Start Orvano”If Orvano isn’t running on your machine yet, run these three commands. Run Orvano locally explains each one.
mkdir orvano-local && cd orvano-localdocker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/install" ghcr.io/orvanohq/orvano:0.2.0 install --local --yesdocker compose up -d --waitmkdir orvano-local; cd orvano-localdocker run --rm -v "${PWD}:/install" ghcr.io/orvanohq/orvano:0.2.0 install --local --yesdocker compose up -d --waitYou should see every service report Healthy, and the console open at http://localhost:7700.
2. Set up your project in the console
Section titled “2. Set up your project in the console”-
Open the setup link the installer printed and create the first admin. You land in your personal org.


-
Select Create project, name it, and select Create project again. Setting it up takes a few seconds.


-
On the project’s overview, copy the Project ID.


-
Open API keys and select Create key. Name it, tick the scopes Users read and Users write, and select Create key. Copy the key it shows: you only see it once. A key can do only what its scopes allow, so this one can read and create users and nothing else.


3. Create the server
Section titled “3. Create the server”You need the .NET 10 SDK. Make a folder with this project file, named DotnetQuickstart.csproj. It pins the Orvano package to 0.2.0, the version of the Orvano you’re running.
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net10.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup>
<ItemGroup> <PackageReference Include="Orvano" Version="0.2.0" /> </ItemGroup></Project>Every piece of code below goes in one Program.cs, in the order shown.
4. Add your settings
Section titled “4. Add your settings”The server reads its settings from the environment, so the API key never sits in your code or your repo. Set them in the terminal you run the server from, with your project ID and the key you copied:
export ORVANO_ENDPOINT=http://localhost:7700export ORVANO_PROJECT=your-project-idexport ORVANO_API_KEY=your-api-key$env:ORVANO_ENDPOINT = "http://localhost:7700"$env:ORVANO_PROJECT = "your-project-id"$env:ORVANO_API_KEY = "your-api-key"5. Talk to Orvano
Section titled “5. Talk to Orvano”Make one client for the server. It sends the API key with every call, and it checks access tokens against your project’s public keys, which it caches for 10 minutes:
// The server client. It sends your API key with every call and checks your users' access tokens against the// project's public keys. Settings come from the environment, so the key never sits in code.var endpoint = new Uri(Setting("ORVANO_ENDPOINT", "http://localhost:7700"));using var orvano = new OrvanoClient(new OrvanoClientOptions(endpoint){ Project = Setting("ORVANO_PROJECT"), ApiKey = Setting("ORVANO_API_KEY"),});6. Create a user
Section titled “6. Create a user”On a server, you create users with the API key, for example when you import them from another system. dotnet run -- create-user creates one and exits before the web server starts; it runs on your machine and is never an open HTTP route:
// `dotnet run -- create-user <email> <password> [name]` creates a user with the API key, then exits.if (args is ["create-user", var email, var password, ..]){ try { var created = await orvano.Users.CreateAsync( new CreateUserRequest(email, password, Name: args.Length > 3 ? args[3] : null)); Console.WriteLine($"Created user {created.Id} ({created.Email})."); return 0; } catch (OrvanoException error) { Console.Error.WriteLine($"Orvano refused: {error.Message} ({error.Code})"); return 1; }}Run it:
dotnet run -- create-user ada@example.com 'a long password' 'Ada Lovelace'You should see Created user with the new user’s ID. The user is in the console too, on your project’s Users page.
7. Check a user’s token on your server
Section titled “7. Check a user’s token on your server”GET /me reads the token from Authorization: Bearer <token>. VerifyAccessTokenAsync checks its signature, its project, and its expiry, and answers with the user’s ID, so the server loads the user with Users.GetAsync. A missing or bad token gets 401.
// GET /me checks the access token in `Authorization: Bearer <token>`, then answers with the user it belongs to.// A missing or bad token gets 401.app.MapGet("/me", async (HttpContext context, OrvanoClient orvano, CancellationToken cancellationToken) =>{ var header = context.Request.Headers.Authorization.ToString(); if (!header.StartsWith("Bearer ", StringComparison.Ordinal)) return Unauthorized(context);
try { // Checks the signature, the project, and the expiry, without a call to Orvano once the keys are cached. var token = await orvano.VerifyAccessTokenAsync( header["Bearer ".Length..], cancellationToken: cancellationToken); var user = await orvano.Users.GetAsync(token.UserId, cancellationToken); return Results.Ok(new { user.Id, user.Email, user.Name }); } catch (OrvanoException error) when (error.Status == StatusCodes.Status401Unauthorized) { // invalid_token or token_expired; anything else is a real failure. return Unauthorized(context); }});
static IResult Unauthorized(HttpContext context){ context.Response.Headers.WWWAuthenticate = "Bearer"; return Results.Json( new { error = "Send a valid access token." }, statusCode: StatusCodes.Status401Unauthorized);}The whole Program.cs, with the web host and the settings helper
using Orvano;
// The server client. It sends your API key with every call and checks your users' access tokens against the// project's public keys. Settings come from the environment, so the key never sits in code.var endpoint = new Uri(Setting("ORVANO_ENDPOINT", "http://localhost:7700"));using var orvano = new OrvanoClient(new OrvanoClientOptions(endpoint){ Project = Setting("ORVANO_PROJECT"), ApiKey = Setting("ORVANO_API_KEY"),});
// `dotnet run -- create-user <email> <password> [name]` creates a user with the API key, then exits.if (args is ["create-user", var email, var password, ..]){ try { var created = await orvano.Users.CreateAsync( new CreateUserRequest(email, password, Name: args.Length > 3 ? args[3] : null)); Console.WriteLine($"Created user {created.Id} ({created.Email})."); return 0; } catch (OrvanoException error) { Console.Error.WriteLine($"Orvano refused: {error.Message} ({error.Code})"); return 1; }}
var builder = WebApplication.CreateBuilder(args);builder.WebHost.UseUrls("http://localhost:3002");builder.Services.AddSingleton(orvano);var app = builder.Build();
// GET /me checks the access token in `Authorization: Bearer <token>`, then answers with the user it belongs to.// A missing or bad token gets 401.app.MapGet("/me", async (HttpContext context, OrvanoClient orvano, CancellationToken cancellationToken) =>{ var header = context.Request.Headers.Authorization.ToString(); if (!header.StartsWith("Bearer ", StringComparison.Ordinal)) return Unauthorized(context);
try { // Checks the signature, the project, and the expiry, without a call to Orvano once the keys are cached. var token = await orvano.VerifyAccessTokenAsync( header["Bearer ".Length..], cancellationToken: cancellationToken); var user = await orvano.Users.GetAsync(token.UserId, cancellationToken); return Results.Ok(new { user.Id, user.Email, user.Name }); } catch (OrvanoException error) when (error.Status == StatusCodes.Status401Unauthorized) { // invalid_token or token_expired; anything else is a real failure. return Unauthorized(context); }});
static IResult Unauthorized(HttpContext context){ context.Response.Headers.WWWAuthenticate = "Bearer"; return Results.Json( new { error = "Send a valid access token." }, statusCode: StatusCodes.Status401Unauthorized);}
app.Run();return 0;
static string Setting(string name, string? fallback = null){ var value = Environment.GetEnvironmentVariable(name) ?? fallback; return string.IsNullOrEmpty(value) ? throw new InvalidOperationException($"Set the {name} environment variable.") : value;}8. Try it
Section titled “8. Try it”Start the server with dotnet run. You should see Now listening on: http://localhost:3002.
In another terminal (with the same settings), sign in as the user you created to get an access token, the way an app’s sign in would, and call /me with it:
curl -s http://localhost:7700/v1/account/sessions/password \ -H "X-Orvano-Project: $ORVANO_PROJECT" -H "Content-Type: application/json" \ -d '{"email":"ada@example.com","password":"a long password"}'
# Copy session.accessToken from the answer, then:TOKEN=paste-the-access-token-herecurl -i http://localhost:3002/me -H "Authorization: Bearer $TOKEN"$session = (Invoke-RestMethod -Method Post -Uri http://localhost:7700/v1/account/sessions/password ` -Headers @{ "X-Orvano-Project" = $env:ORVANO_PROJECT } -ContentType "application/json" ` -Body '{"email":"ada@example.com","password":"a long password"}').sessionInvoke-RestMethod -Uri http://localhost:3002/me ` -Headers @{ Authorization = "Bearer $($session.accessToken)" }- You should see
200 OKand the user’s ID, email, and name. - Call
/meagain without theAuthorizationheader. You should see401 Unauthorized. - Change one character of the token and call it again. You should see
401again: the signature no longer matches.
Access tokens last 15 minutes. In a real app, the client SDK refreshes the session and sends a fresh token with each call.
- Learn what a token proves, and when to ask Orvano instead: Verify tokens on your server.
- List, block, and delete users from your server: Manage users with an API key.
- See everything the
Orvanopackage can do: .NET SDK.