ZKRINET Docs
Building blocks

BankID

On Zkrinet: add the block under Building blocks in the panel — one click, your own code is untouched and a snapshot is taken first. The installation steps below apply if you run PocketBase yourself, for example after an export.

Log in with Swedish BankID through an EU-based OIDC broker: Idura (formerly Criipto, Denmark) or Signicat (Norway). Tested against PocketBase 0.40.4.

Honestly, what is what

The login itself is PocketBase's own. PocketBase has a built-in OpenID Connect provider (the slots oidc, oidc2 and oidc3 under OAuth2). It handles the login page, PKCE, the code exchange, the token and the user data. The plugin does not build its own login. It adds four things:

  1. A check that tells you exactly what is missing and compares your settings against the broker's discovery document.
  2. Preparation of users: BankID does not hand out an email address, and without this change PocketBase cannot create the account (MEASURED: 400 email: Cannot be blank). Email becomes optional for BankID accounts but is still required for regular sign-up.
  3. Name: the broker's name, or first name + last name (given_name + family_name) if name is missing.
  4. Personal identity number (personnummer): not stored by default, and removed from the response to the browser (see GDPR below). If you want, you can turn on linking via hash, so that the account survives a change of broker.

Install

Copy pb_hooks/ and pb_migrations/ into the project and restart. Set Application URL under Settings to your real address (https).

The check

GET /api/zk/bankid/kontroll                                              (superuser)
GET /api/zk/bankid/kontroll?discovery=https://…/.well-known/openid-configuration

Responds with:

Without ?discovery=, <first issuer>/.well-known/openid-configuration is fetched, or otherwise the same path on the Auth URL's address. At Signicat the issuer is under a path, so in that case give the address from Signicat's Dashboard (Well-Known URL).

Step by step: Idura

  1. Create an account at Idura and an Application in the Idura Dashboard. You get a domain of the form ditt-namn.idura.broker (test domain first; the production domain is registered separately).
  2. Add the callback address from the check as the callback URL. Turn on OAuth2 Code Flow and create a client secret. Copy the Client ID (realm) and the secret.
  3. In PocketBase: Collections → users → ⚙ → OAuth2 → Add provider → OpenID Connect (the slot oidc).
    • Client ID + Client secret from step 2. Display name: BankID.
    • Run GET /api/zk/bankid/kontroll?discovery=https://ditt-namn.idura.broker/.well-known/openid-configuration and paste in forslag: Auth URL (ends with ?acr_values=urn:grn:authn:se:bankid), Token URL, User info URL.
  4. Run the check again until varningar is empty.
  5. Test BankID: Idura refers to BankID's test portal (developers.bankid.com → test portal). The BankID app can only be in test or production mode, so preferably use a separate phone. You choose the name and a valid test personal identity number yourself.
  6. Production: according to Idura, the BankID certificate requires an EU VAT number, and Idura orders it via Swedbank (8–15 business days).

Other BankID variants at Idura: urn:grn:authn:se:bankid:same-device (same device) and urn:grn:authn:se:bankid:qr (QR).

Step by step: Signicat

  1. Create a sandbox account at Signicat. According to Signicat, Swedish BankID can be added in the sandbox without an agreement (eID Hub → ID Methods).
  2. Create an OIDC client, add the callback address as the redirect URI, and create a secret.
  3. Scopes: PocketBase always requests openid email profile and this cannot be changed. Allow those three on the client (the check warns if discovery does not list them).
  4. In PocketBase: OpenID Connect as above. Get the addresses with GET /api/zk/bankid/kontroll?discovery=<Well-Known URL from Signicat's Dashboard>. The Auth URL should end with ?acr_values=idp:sbid.
  5. Test BankID: the same test portal at BankID as above.

Prices: see each provider. We have not been able to cite a source for any prices.

The button in your app

const { record, meta } = await pb.collection("users").authWithOAuth2({ provider: "oidc" })

BankID accounts have no email (record.email === ""). If the app needs to email the user, you have to ask for the address separately. Whether the account is linked to BankID, the user can see for themselves (MEASURED):

const kopplingar = await pb.collection("_externalAuths").getFullList({ filter: 'provider = "oidc"' })

Personal identity number and GDPR

Default: the personal identity number is stored nowhere. The plugin removes it from the response to the browser, because PocketBase otherwise sends along the entire broker response (meta.rawUser), including the personal identity number in plain text (MEASURED). The test searches all database files in pb_data for the personal identity number, byte by byte. What is stored is the broker's sub (in _externalAuths) and the name.

sub is a pseudonym per broker: at Idura it is, according to the documentation, unique per user and Idura tenant. This means: if you change broker (or tenant), every user gets a new sub and thereby a new, empty account. We state this plainly because it is a lock-in.

Optional: linking via hash. Set ZK_BANKID_PNR_NYCKEL (at least 32 random characters) and HMAC-SHA256(personnummer, nyckel) is stored in the hidden field users.bankid_pnr_hmac. If the same person logs in with a new sub, the login is linked to the same account (tested: change within the same slot and change between slots). Consequences, honestly:

The broker processes personal data on your behalf. Sign a data processing agreement with it.

Security

Limitations

What we have NOT been able to measure

Environment variables

VariableDefaultWhat
ZK_BANKID_LEVERANTORERoidcWhich OIDC slots are BankID (e.g. oidc,oidc2 during a broker change).
ZK_BANKID_PNR_NYCKEL(off)At least 32 characters. Turns on linking via personal identity number hash.

Testing

./bank.py bankid

19 tests against a fake broker on 127.0.0.1 (discovery, token, userinfo, JWKS). Nothing reaches the internet. The tests cover the migration, the email requirement (including via batch), the check's warnings, login without email, name, the personal identity number (searched byte by byte in pb_data and in the response), broker change, that another OIDC slot cannot take over an account, the hidden field and PocketBase's own id_token verification. The security tests and the warnings are verified with mutants: 14 of 14 broken protections were caught. The ID token test signs with RS256 and requires the Python package cryptography on the test machine. The plugin itself has no dependencies.