Register trainee teachers - Authentication
Register has three sign in modes and only one is active at a time.
Settings.features.sign_in_method selects which one.
-
dfe-sign-inis the default and is used in staging, sandbox, production data and production. -
otpis the fallback for when DSI is unavailable. -
personais used in development, review and QA.
Basic Auth
The QA, review and production data environments are protected by basic auth. The username and
password can be provided by a Register team member. It is on by default, so a new environment gets
it unless features.basic_auth is set to false. The /metrics path is not protected.
DSI
DSI is DfE's identity service, used by services across the organisation including Register to authenticate their users. You need a DfE Sign in account to access the staging, sandbox, production data and production environments.
It uses OpenID Connect (OIDC) on top of OAuth 2.0. Register uses it to authenticate only. DSI
answers who the person is. Register decides what they can do from its own user record, the
providers or training partners attached to it and its system_admin flag. No roles or organisation
data come from DSI and the access token it issues is never stored or used.
User records
Register keeps its own users table. A DSI account on its own does not grant access. A system
admin has to create the user in Register first and attach them to a provider or training partner.
Signing in never creates a record. A user with no record is sent to /sign-in/user-not-found.
On the first sign in the DSI sub is stored on the record as dfe_sign_in_uid and from then on
that is what matches the person to their record instead of their email. Removing a user is a soft
delete (discarded_at) and every lookup is scoped to User.kept, so a removed user loses access
on their next request.
sequenceDiagram
actor C as Client
participant R as Register
participant D as DSI
participant DB as users table
C->>R: GET /trainees
R-->>C: redirect to /sign-in
C->>R: Sign in
R-->>C: redirect to DSI with state and nonce
C->>D: authenticate
D-->>C: redirect back with code and state
C->>R: GET /auth/dfe/callback
R->>D: exchange the code
D-->>R: access token and ID token
R->>R: verify the ID token
R->>R: reset the session and store the identity
R->>DB: find the user by sub, then by email
alt user found
R->>DB: stamp last_signed_in_at and store the sub
R-->>C: redirect to the path first requested
else no user
R-->>C: redirect to /sign-in/user-not-found
end
Authentication Flow:
- A client either attempts to click Sign in or access any protected route like
/trainees/. In the second case the requested path is saved in the session and the client is redirected to/sign-in. - A redirect is performed to DSI with the authorization code flow, sending a state and a nonce.
- The user authenticates with DSI. Their credentials are entered on DSI, not on Register.
- Once DSI authenticates the user, the client is redirected back to Register with the DSI code and state.
- Register checks if the state received from DSI matches for that attempt, then posts the code back to DSI to exchange it for an access token and an ID token.
- Register verifies the ID token against DSI's published keys and checks its issuer, audience, expiry and nonce.
- Register resets the session and stores the identity from the ID token (email, sub, name). The session itself is kept in the database and the cookie holds only the session id.
- Register looks up the user record by DSI sub, falling back to email. If there is no record the
session is discarded and the client is sent to
/sign-in/user-not-found. - If the user is found,
last_signed_in_atis stamped and the DSI sub is stored against the record. The client is redirected to the path originally requested, or to/organisationsif they belong to more than one. - The session lasts two hours of inactivity. DSI is not contacted again until sign out.
Sign out
Register ends its own session first. It then sends the client to DSI with the ID token it stored at sign in, which asks DSI to end its session too. DSI then redirects the client back to Register.
Configuration
DSI is one of three sign in modes. Settings.features.sign_in_method selects which one the app
uses and dfe-sign-in is the default. The other two are otp and persona, used in
non-production environments. The value is read at boot in config/initializers/omniauth.rb, which
mounts the OmniAuth strategy and in config/routes.rb, which defines the callback routes.
The DSI client settings live under dfe_sign_in in config/settings.yml, with per-environment
overrides:
| Key | Meaning |
|---|---|
identifier |
OIDC client id. rtt
|
secret |
OIDC client secret. The value in the file is a placeholder and the real one is injected from the environment. Sign in fails if it is not set |
issuer |
OIDC issuer. Test https://test-oidc.signin.education.gov.uk, pre-production https://pp-oidc.signin.education.gov.uk, production https://oidc.signin.education.gov.uk
|
dfe_sign_in.profile is also set per environment but nothing in the app reads it.
Any of these can be overridden per environment, see configuration.
OTP
OTP is the fallback sign in mode, used when DSI is unavailable. Instead of signing in with DSI the user gets a one time code by email. The sign in page says that DfE Sign in is currently unavailable.
No environment uses it by default. It is switched on by setting
Settings.features.sign_in_method to otp, which also removes the DSI routes because the modes
are mutually exclusive.
The user still needs a record in Register. A code only proves that the person has access to the mailbox.
Sign out only ends the Register session, because there is no identity provider to sign out of.
Authentication Flow:
- A client goes to
/request-sign-in-codeand enters their email. - Register validates the email and allows two requests a minute.
- Register stores the email and a new salt in the session.
- If the email belongs to a user, Register generates a code and sends it with GOV.UK Notify. If it
does not, nothing is sent. In both cases the client is redirected to
/sign-in-code. - The client enters the code.
- Register rebuilds the code from the user's secret and the salt, then compares it. Five attempts are allowed every five minutes and the code is valid for ten minutes.
- If the code matches, Register stores the email in the session and deletes the salt so the code cannot be used again.
- From then on Register matches the session to a user record by email. The session lasts two hours of inactivity, the same as DSI.
Codes
Each user has a permanent secret in the users table, stored encrypted. The code is generated from
that secret plus a salt that only exists in the current session, so a code only works in the
browser that asked for it.
Configuration
Enabled when Settings.features.sign_in_method == "otp". The Notify template is
govuk_notify.otp_email_template_id and the request and verification limits are under
otp.throttling in config/settings.yml.
Personas
Personas are a fake sign in mode for non-production environments. Instead of DSI you get a page listing seeded users with a button to sign in as any of them. It is used in development, review and QA.
There is no password and no identity provider. Everything after the sign in itself behaves in the same way as DSI. Sign out only ends the Register session, because there is no identity provider to sign out of.
Authentication Flow:
- A client goes to
/sign-inand follows the link to/personas, or is redirected there from a protected route. -
/personaslists the personas with the provider or training partner they belong to. - Clicking "Login as X" posts that persona's email and name to
/auth/developer/callback. - Register stores the identity in the session in the same way as DSI.
- Register looks up the user record by email. If there is no record the client is sent to
/sign-in/user-not-found.
Who appears in the list
Five personas are defined in config/initializers/personas.rb. One is a system admin and the rest
belong to providers or training partners. The list also includes one user from each of the ten
providers with the most trainees, which is what makes it useful against a sanitised database dump.
You can add your own persona locally, see setting up development.
Seeding
Personas are not created when you sign in. They come from bin/rails example_data:generate, which
creates the users, their providers and a set of trainees.
On review apps the database is reset and the example data is created again on every deploy. Anything you create on a review app is lost on the next deploy.
Configuration
Enabled when Settings.features.sign_in_method == "persona".