kineuroExperimental Neuroradiology Research
Karolinska Institutet

An identity provider decides

The desk as a confidential client: one command registers it, the provider says who a person is and which of its groups they are in, and the desk's groups follow.

The wizard does this for you. This page is what it does, for when you would rather do it yourself.

For an organisation that already has single sign-on. The desk becomes a confidential client of the provider. The provider tells the desk who a person is and which of its groups they are in, the desk’s groups follow those and decide what the person may do, and the desk signs the token the engine, Kvasir and the assistant receive. We run this against Authentik; anything that speaks OpenID Connect works the same way.

Register the desk

The desk can register itself, which is one command and less error-prone than the provider’s forms. It needs an API token of the provider, in a file:

nils-desk register --authentik https://auth.example.org \
  --token ./authentik-api-token \
  --origin https://nils.example.org \
  --allow nils \
  --bind reader=nils --bind reviewer=nils --bind assist=nils \
  --bind operator=staff --bind admin=admin \
  --secret-file ./nils-desk.client-secret

It creates the application and its provider if they are not there, is safe to run again, binds the groups you name, writes the client secret at mode 600, and prints three things: the engine’s flags and Kvasir’s auth, both trusting the desk beside the provider, and the [oidc] table for your configuration file. When you choose an Authentik, the wizard runs this command during the install: it binds reader, reviewer and assist to the group of the people who use NILS, and all five to the group of those who run it.

  • --allow GROUP names a group that may open the application at all; it repeats.
  • --bind NAME=GROUP gives a group at the provider an entitlement; it repeats. The five are the old names reader, reviewer, operator, admin and assist, and each stands for its set, as the first four groups says. A group at the desk that follows a group at the provider gives its people pages whether or not that group is bound.
  • --audience is the audience of the tokens the desk signs, as its [local] table names it; the default is nils.
  • --slug and --name set what it is called at the provider; the defaults are nils and NILS.

Once the desk is registered at a provider, a person with work on the install can move the desk to it from the desk itself: Settings, People, Change how people sign in, with the issuer, the client id, the secret and the claim of its entitlements; see change how people sign in. Registering the desk at an Authentik stays at the machine.

The desk

bind = "127.0.0.1:7200"
origin = "https://nils.example.org"
mode = "oidc"
store = "nils-desk.sqlite"

[oidc]
issuer = "https://auth.example.org/application/o/nils/"
client_id = "..."
client_secret_file = "nils-desk.client-secret"
scopes = ["openid", "profile", "email", "offline_access", "entitlements"]
groups_claim = "groups"
roles_claim = "roles"

[engine]
url = "http://127.0.0.1:8437"
  • groups_claim is the claim the provider’s groups arrive in, groups unless set. A group at the desk follows names in it.
  • roles_claim is the claim of the entitlements the registration binds, and each still stands for its set, beside what the groups give.

The provider’s groups are read when a person signs in, so a change at the provider counts from their next sign-in; a change at the desk counts at once. The desk signs the parts’ tokens with a key of its own, made at first start, as in local mode.

The engine

Exactly what the registration printed:

nils serve --auth oidc \
  --oidc-trust issuer=https://nils.example.org,audience=nils,jwks=https://nils.example.org/.well-known/jwks.json,keep_subject=true \
  --oidc-trust issuer=https://auth.example.org/application/o/nils/,audience=<client id>,jwks=https://auth.example.org/application/o/nils/jwks/ \
  --oidc-groups-claim roles \
  --role reader=reader --role reviewer=reviewer \
  --role operator=operator --role admin=admin \
  --bind 127.0.0.1:8437 --registry /srv/nils/registry --pack-dir /srv/nils/packs

The first entry trusts the desk. Its tokens carry each person’s grants and detail, and name the person as the provider’s subject at the provider’s host; keep_subject=true keeps that name as it is, so people keep the name the engine’s history and Kvasir already know them by. The second entry trusts the provider itself, for a program that calls the engine with the provider’s own token, whose entitlements --role maps. Kvasir trusts the two the same way: see the model and Kvasir.

Making the provider by hand

If you would rather use the provider’s own interface, three settings matter and are easy to miss:

  1. The client is confidential, and its secret goes in the file the desk names.
  2. The redirect URI is exactly {origin}/desk/callback, matched strictly.
  3. The provider allows the authorization code and refresh grants. A provider with no grants allowed fails the first sign in with invalid_request The request is otherwise malformed, which says nothing about why.

Beyond that: the scopes above, the provider’s groups in the claim groups_claim names, any entitlements in the claim roles_claim names, and a signing key the JWKS publishes.

Signing out

The person’s name at the top right opens a menu with Sign out and Sign in as someone else. Every item is reached by Tab, and Escape closes it.

  • Sign out ends the desk’s session and sends the browser to the provider’s end-session page, with the id token of the sign-in and the desk’s front page as the way back. The provider’s session ends too, so the next sign-in asks who it is instead of signing the same person in again.
  • Sign in as someone else, also on the sign-in page, asks the provider to sign in afresh (/desk/login?prompt=login), so a person can pick another account than the one the provider already holds in this browser.

A desk session never outlives the provider’s session it came from. The provider may say a session has ended at POST /desk/backchannel-logout (OpenID Connect Back-Channel Logout), and the desk’s sessions that came from it end at once; the token is checked against the provider’s keys, its issuer and this client. Each refresh of the provider’s tokens, about every fifteen minutes, is checked as well: a refused refresh, or an id token for another person or another provider session, ends the desk’s session and the desk shows the sign-in. A new sign-in in a browser that held a session ends the earlier one, and the bound of twelve hours stays.

For this, three settings of the provider matter:

  1. The desk’s front page, {origin}/, is a post-logout redirect URI, matched strictly, so the provider sends the browser back after a sign out.
  2. The provider has an invalidation flow that ends the person’s session there, such as Authentik’s default-invalidation-flow. Without one, a sign out leaves the person signed in at the provider, and the next sign-in takes them up silently.
  3. The provider’s back-channel logout URL is {origin}/desk/backchannel-logout, so a sign out elsewhere, or a session an administrator ends at the provider, ends the desk’s session too.

nils-desk register sets the first two, and is safe to run again over a desk registered before. The third is set in the provider’s own interface; without it, a session ended at the provider still ends the desk’s at its next token refresh.

Who holds what

A person in no group the desk follows, and with no entitlement, signs in successfully and holds nothing. The desk says exactly that: the account exists and holds no grant, and pages are given on the People page, through a group that follows one of the provider’s or to that person alone. It shows no error: it is a state, not a fault.

The People page lists the people who have signed in, with their groups. People are added at the provider, not at the desk.

Updating signs everyone out once. A desk that signs people in through a provider now signs the parts’ tokens itself, so its first start after updating from a release before groups ends every session, and each person signs in again.

Updated 17 October 2026