proposed federation.upstream-sign-in

Sign in, or sign up, through one of the realm's upstream identity providers

The person picks a provider on the first sign-in step, or the authorization request names one with qauth_upstream (a hidden provider too). The provider's method starts, and what it needs back (state, nonce, the PKCE verifier) is kept server-side for ten minutes, bound to the interaction and to the browser's flow cookie; the waiting page sends the browser to the provider. At the callback only the browser that started may take that pending sign-in, once. The account is looked up by (realm, provider type, sub) alone, never by an email or another attribute; a known credential stands only under the issuer it was created with, and an unknown one creates a user, the credential and the provider's claims as attributes in one transaction, when the realm allows sign-up. The sign-in engine then applies the realm's policy, an upstream sign-in ranking with a password: an account that holds a passkey still passes it.

What this view shows

the sign-in flow around the upstream step, from the pick or the routed request to the response.

Open world: what is not drawn here is not ruled out. Only the guarantees below are binding.

Left out of this view:

The main story

When: The realm has the provider on, hidden or not, the provider's method starts and answers where the browser goes, the browser that started brings the flow cookie the pending sign-in is bound to, the method proved an identity, its assurance capped at low, none does, whatever another account holds as its email, the identity's issuer (the ID token's iss) is the one the credential was created under, credentialState is active, the provider's verified switch is on: an account it creates counts as verified, the provider gave an email, the realm's sign-up switch (accounts.allowSignUp) is on, the realm asks nothing more of the account, the engine takes the proof, INTERACTION_UI_ENABLED is off: the server-rendered pages, which show no further step.

  1. Wait for a person who chooses an upstream provider: the person picks a provider: the upstream alternative of the first sign-in step (Interaction API), or a provider link of /ui/login, which sends the browser back to the authorization request with qauth_upstream
  2. Find the provider among the realm's: the interaction starts the provider in its first round, recorded by the engine
  3. Ask the provider's method to start: start() answers
  4. Keep the pending sign-in, bound to the interaction and the flow cookie: the waiting page (a meta refresh, a link for a browser that does not follow it, and the way back) sends the browser to the provider; through the Interaction API, the answer's location does
  5. Wait while the person is at the provider: the provider sends the browser to /upstream/<type>/callback (a form_post is bounced to the GET route)
  6. Hand the pending sign-in only to the browser that started it: the pending sign-in is taken and handed to the method's callback
  7. Turn the method's answer into the step's completion: an identity and its attributes
  8. Look the credential up by (realm, provider type, sub) alone: one credential or none
  9. Decide what the credential found allows: an unknown identity, in a realm that allows sign-up
  10. Create the user, the credential and the attributes in one transaction: a new account
  11. Hand the account to the sign-in engine as proved by the upstream step: the engine answers its round
  12. Go on as the engine's round says: signed in: the pages start the authorization request again under the new session

Result: Send the signed-in browser back to the authorization request.

The flow

the person picks a provider: the upstream alternative of the first sign-in step (Interaction API), or a provider link of /ui/login, which sends the browser back to the authorization request with qauth_upstream an authorization request that needs a sign-in names a provider with qauth_upstream; a hidden provider is reached this way only the realm has no such provider on the interaction starts the provider in its first round, recorded by the engine start() answers the waiting page (a meta refresh, a link for a browser that does not follow it, and the way back) sends the browser to the provider; through the Interaction API, the answer's location does the provider sends the browser to /upstream/<type>/callback (a form_post is bounced to the GET route) no pending sign-in for this browser: a replay in another browser, a forged callback, or a spent one the pending sign-in is taken and handed to the method's callback the person cancelled or declined at the provider the provider answered another error the provider's answer failed a check, or the provider could not be reached the identity names no issuer: its sub could not be told apart from another issuer's the method broke the contract; its error is logged for the operator an identity and its attributes one credential or none the credential was created under another issuer: a sub is unique only within its issuer (OIDC Core §5.7), so the old credential never signs in under the new one the credential is disabled the credential's user, and whether the account counts as verified, as its creation recorded an unknown identity, in a realm that does not allow sign-up an unknown identity the provider's verified switch leaves unverified, with no address for a code or on the pages, which show no code step: it could never sign in, so nothing is written an unknown identity, in a realm that allows sign-up a new account the engine answers its round the engine refuses the proof, and audits why a further step the pages cannot show: the sign-in fails closed a further step (the passkey, a code): the ceremony page shows it signed in: the pages start the authorization request again under the new session signed in: the interaction finishes the authorization request as the Interaction API does start idle Wait for a person who chooses an upstream provider idle · server step The realm's generic OpenID Connect providers (realm_upstream_providers, oidc_<alias>) and its enabled AuthMethod plugins. A routed request with a type the realm has not, or has off, is not routed: the routing parameter is ignored and the first sign-in step shows, so this flow never sees it; a provider link or a start for such a type is refused here. Find the provider among the realm's resolvingProvider · server step start() answers the provider's authorization request, the transaction and the binding (the state). Ask the provider's method to start starting · server step Redis keeps the transaction under a hash of its binding for ten minutes, with the interaction, the flow cookie's binding and the way back to the first step. The transaction never reaches the browser. Keep the pending sign-in, bound to the interaction and the flow cookie keepingPending · server step atProvider Wait while the person is at the provider atProvider · person The callback's binding names the pending sign-in; it is read first, and taken once only when the request carries the flow cookie it is bound to, of the same realm and provider type. A callback in another browser spends nothing: the method never runs, no code is redeemed and the pending sign-in stays for its own browser. Hand the pending sign-in only to the browser that started it checkingPending · server step federation.upstream-step's exits, one by one. Turn the method's answer into the step's completion completingCallback · server step Never by an email or another attribute: a person whose address another account holds gets an account of their own, and nothing is matched or merged. Look the credential up by (realm, provider type, sub) alone lookingUpCredential · server step checkingCredential Decide what the credential found allows checkingCredential · server step The credential holds the provider type (oidc_<alias> for a generic OpenID Connect provider), the sub, the issuer (the ID token's iss) and whether the account counts as verified: yes, unless the provider's verified switch is off. Each claim the method gave becomes an attribute whose source is the provider type; the email attribute is verified only when the provider said so and the switch is on. user.registered (method upstream) commits with them. A callback that loses the race to create the same identity reads the account the other created. Create the user, the credential and the attributes in one transaction creatingAccount · server step The upstream step ranks with a password, below a passkey, under the realm's MFA policy. The interaction is answered at the revision the start recorded, so a callback for an interaction that moved on is refused. Hand the account to the sign-in engine as proved by the upstream step completingStep · server step decidingRound Go on as the engine's round says decidingRound · server step user.signed_in (method upstream) is recorded, and the user's last login. Send the authorization code to the client redirect with code + state + iss user.signed_in (method upstream) is recorded. /oauth/authorize checks the request from scratch under the new session, and goes on to the consent or the code. Send the signed-in browser back to the authorization request no redirect, page shown step_continues.ceremony_page Send the browser to the ceremony page for the next step no redirect, page shown user.sign_in_failed is recorded with the reason declined. Nothing goes to the client: the person has not refused the client's request, and may pick another way. Show the provider-error page for a cancel at the provider no redirect, page shown user.sign_in_failed is recorded with the reason provider_error. Show the provider-error page for an error the provider answered no redirect, page shown user.sign_in_failed is recorded with the reason upstream_unavailable. Refuse a provider that is off or cannot start no redirect, page shown user.sign_in_failed is recorded with the reason transaction_mismatch. No code is redeemed and no account is created. Refuse a callback with no pending sign-in for this browser no redirect, page shown user.sign_in_failed is recorded with the method's reason: invalid_response, upstream_unavailable or verification_refused. Refuse an answer of the provider that fails a check no redirect, page shown user.sign_in_failed is recorded with the reason method_fault. Refuse the answer of a method that broke the contract no redirect, page shown user.sign_in_failed is recorded with the reason issuer_mismatch and the credential's user. Refuse a known sub under another issuer no redirect, page shown user.sign_in_failed is recorded with the reason credential_disabled. Refuse a disabled credential no redirect, page shown user.sign_in_failed is recorded with the reason sign_up_disabled. No account is created. Refuse an unknown identity in a realm that does not allow sign-up no redirect, page shown user.sign_in_failed is recorded with the reason verification_unavailable. No account is created. Refuse a new account nobody could verify no redirect, page shown The engine records user.sign_in_failed with its own reason. Refuse a proof the engine refuses no redirect, page shown user.sign_in_failed is recorded with the reason step_not_available. The account's passkey, or QAuth's own verification, is asked only on the ceremony page. Refuse a sign-in the pages cannot finish no redirect, page shown
the person picks a provider: the upstream alternative of the first sign-in step (Interaction API), or a provider link of /ui/login, which sends the browser back to the authorization request with qauth_upstream an authorization request that needs a sign-in names a provider with qauth_upstream; a hidden provider is reached this way only the realm has no such provider on the interaction starts the provider in its first round, recorded by the engine start() answers fails: start() rejects, or its answer fails a check; the browser goes nowhere the waiting page (a meta refresh, a link for a browser that does not follow it, and the way back) sends the browser to the provider; through the Interaction API, the answer's location does fails: the store cannot keep it: the engine reports it, and nothing starts the provider sends the browser to /upstream/<type>/callback (a form_post is bounced to the GET route) the person never comes back no pending sign-in for this browser: a replay in another browser, a forged callback, or a spent one the pending sign-in is taken and handed to the method's callback the person cancelled or declined at the provider the provider answered another error the provider's answer failed a check, or the provider could not be reached the identity names no issuer: its sub could not be told apart from another issuer's the method broke the contract; its error is logged for the operator an identity and its attributes one credential or none fails: the database fails the credential was created under another issuer: a sub is unique only within its issuer (OIDC Core §5.7), so the old credential never signs in under the new one the credential is disabled the credential's user, and whether the account counts as verified, as its creation recorded an unknown identity, in a realm that does not allow sign-up an unknown identity the provider's verified switch leaves unverified, with no address for a code or on the pages, which show no code step: it could never sign in, so nothing is written an unknown identity, in a realm that allows sign-up a new account fails: the database fails; nothing of the account is written the engine answers its round fails: the interaction store or the failure ladder cannot answer the engine refuses the proof, and audits why a further step the pages cannot show: the sign-in fails closed a further step (the passkey, a code): the ceremony page shows it signed in: the pages start the authorization request again under the new session signed in: the interaction finishes the authorization request as the Interaction API does start idle Wait for a person who chooses an upstream provider idle · server step The realm's generic OpenID Connect providers (realm_upstream_providers, oidc_<alias>) and its enabled AuthMethod plugins. A routed request with a type the realm has not, or has off, is not routed: the routing parameter is ignored and the first sign-in step shows, so this flow never sees it; a provider link or a start for such a type is refused here. Find the provider among the realm's resolvingProvider · server step start() answers the provider's authorization request, the transaction and the binding (the state). Ask the provider's method to start starting · server step Redis keeps the transaction under a hash of its binding for ten minutes, with the interaction, the flow cookie's binding and the way back to the first step. The transaction never reaches the browser. Keep the pending sign-in, bound to the interaction and the flow cookie keepingPending · server step atProvider Wait while the person is at the provider atProvider · person The callback's binding names the pending sign-in; it is read first, and taken once only when the request carries the flow cookie it is bound to, of the same realm and provider type. A callback in another browser spends nothing: the method never runs, no code is redeemed and the pending sign-in stays for its own browser. Hand the pending sign-in only to the browser that started it checkingPending · server step federation.upstream-step's exits, one by one. Turn the method's answer into the step's completion completingCallback · server step Never by an email or another attribute: a person whose address another account holds gets an account of their own, and nothing is matched or merged. Look the credential up by (realm, provider type, sub) alone lookingUpCredential · server step checkingCredential Decide what the credential found allows checkingCredential · server step The credential holds the provider type (oidc_<alias> for a generic OpenID Connect provider), the sub, the issuer (the ID token's iss) and whether the account counts as verified: yes, unless the provider's verified switch is off. Each claim the method gave becomes an attribute whose source is the provider type; the email attribute is verified only when the provider said so and the switch is on. user.registered (method upstream) commits with them. A callback that loses the race to create the same identity reads the account the other created. Create the user, the credential and the attributes in one transaction creatingAccount · server step The upstream step ranks with a password, below a passkey, under the realm's MFA policy. The interaction is answered at the revision the start recorded, so a callback for an interaction that moved on is refused. Hand the account to the sign-in engine as proved by the upstream step completingStep · server step decidingRound Go on as the engine's round says decidingRound · server step user.signed_in (method upstream) is recorded, and the user's last login. Send the authorization code to the client redirect with code + state + iss user.signed_in (method upstream) is recorded. /oauth/authorize checks the request from scratch under the new session, and goes on to the consent or the code. Send the signed-in browser back to the authorization request no redirect, page shown step_continues.ceremony_page Send the browser to the ceremony page for the next step no redirect, page shown user.sign_in_failed is recorded with the reason declined. Nothing goes to the client: the person has not refused the client's request, and may pick another way. Show the provider-error page for a cancel at the provider no redirect, page shown user.sign_in_failed is recorded with the reason provider_error. Show the provider-error page for an error the provider answered no redirect, page shown user.sign_in_failed is recorded with the reason upstream_unavailable. Refuse a provider that is off or cannot start no redirect, page shown user.sign_in_failed is recorded with the reason transaction_mismatch. No code is redeemed and no account is created. Refuse a callback with no pending sign-in for this browser no redirect, page shown user.sign_in_failed is recorded with the method's reason: invalid_response, upstream_unavailable or verification_refused. Refuse an answer of the provider that fails a check no redirect, page shown user.sign_in_failed is recorded with the reason method_fault. Refuse the answer of a method that broke the contract no redirect, page shown user.sign_in_failed is recorded with the reason issuer_mismatch and the credential's user. Refuse a known sub under another issuer no redirect, page shown user.sign_in_failed is recorded with the reason credential_disabled. Refuse a disabled credential no redirect, page shown user.sign_in_failed is recorded with the reason sign_up_disabled. No account is created. Refuse an unknown identity in a realm that does not allow sign-up no redirect, page shown user.sign_in_failed is recorded with the reason verification_unavailable. No account is created. Refuse a new account nobody could verify no redirect, page shown The engine records user.sign_in_failed with its own reason. Refuse a proof the engine refuses no redirect, page shown user.sign_in_failed is recorded with the reason step_not_available. The account's passkey, or QAuth's own verification, is asked only on the ceremony page. Refuse a sign-in the pages cannot finish no redirect, page shown user.sign_in_failed is recorded with the reason unavailable. Refuse the callback when a store cannot answer no redirect, page shown server_error.store Fail on a database error no redirect, page shown The pending sign-in expires after ten minutes, and the interaction with it. The person did not come back user leaves the page

blue line: the main storyred dashed line: a failure path (shown with “Show failure paths”)dotted line: decided by a rule, nobody actsa person does it (editor, review, approval)dashed grey box: the server decides, nothing is showngreen exit: successred exit: no redirect

What each step answers

StepThe outside world sees
Send the authorization code to the client
code_issued
303 · headers: {"Location":"redirect_uri with code, state when the request had one, and iss","Set-Cookie":"__Host-qauth_session, and __Host-qauth_flow without the finished interaction","Cache-Control":"no-store"}
Send the signed-in browser back to the authorization request
signed_in.authorization_request
303 · headers: {"Location":"/oauth/authorize with the request the interaction held, without qauth_upstream","Set-Cookie":"__Host-qauth_session","Cache-Control":"no-store"}
Send the browser to the ceremony page for the next step
step_continues.ceremony_page
303 · headers: {"Location":"the interaction's ceremony page on the realm's issuer: <issuer>/ui/interaction/<the interaction id>"}
Show the provider-error page for a cancel at the provider
provider_error.declined
200 · headers: {"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"} · body: "Sign-in was not completed": the provider did not sign the person in, with "Choose another way to sign in", a link to the first sign-in step
Show the provider-error page for an error the provider answered
provider_error.provider_error
200 · headers: {"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"} · body: "Sign-in was not completed": the provider did not sign the person in, with "Choose another way to sign in", a link to the first sign-in step
Refuse a provider that is off or cannot start
bad_request.upstream_unavailable
400 · headers: {"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"} · body: "We could not sign you in", the same text for every failure, and the trace id: the request id
Refuse a callback with no pending sign-in for this browser
bad_request.transaction_mismatch
400 · headers: {"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"} · body: "We could not sign you in", the same text for every failure, and the trace id: the request id
Refuse an answer of the provider that fails a check
bad_request.invalid_response
400 · headers: {"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"} · body: "We could not sign you in", the same text for every failure, and the trace id: the request id
Refuse the answer of a method that broke the contract
bad_request.method_fault
400 · headers: {"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"} · body: "We could not sign you in", the same text for every failure, and the trace id: the request id
Refuse a known sub under another issuer
bad_request.issuer_mismatch
400 · headers: {"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"} · body: "We could not sign you in", the same text for every failure, and the trace id: the request id
Refuse a disabled credential
bad_request.credential_disabled
400 · headers: {"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"} · body: "We could not sign you in", the same text for every failure, and the trace id: the request id
Refuse an unknown identity in a realm that does not allow sign-up
bad_request.sign_up_disabled
400 · headers: {"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"} · body: "We could not sign you in", the same text for every failure, and the trace id: the request id
Refuse a new account nobody could verify
bad_request.verification_unavailable
400 · headers: {"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"} · body: "We could not sign you in", the same text for every failure, and the trace id: the request id
Refuse a proof the engine refuses
bad_request.refused
400 · headers: {"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"} · body: "We could not sign you in", the same text for every failure, and the trace id: the request id
Refuse a sign-in the pages cannot finish
bad_request.step_not_available
400 · headers: {"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"} · body: "We could not sign you in", the same text for every failure, and the trace id: the request id
Refuse the callback when a store cannot answer
bad_request.unavailable
400 · headers: {"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"} · body: "We could not sign you in", the same text for every failure, and the trace id: the request id
Fail on a database error
server_error.store
500 · body: the error handler's problem document, with the trace id

How it decides what happens next

Find the provider among the realm's

#IfThen
1the realm has no such provider onRefuse a provider that is off or cannot start
2the interaction starts the provider in its first round, recorded by the engineAsk the provider's method to start

The first matching rule wins.

Exact conditions
#ConditionGoes toSituations
1providerOffbad_request.upstream_unavailable34560 of 69120
2otherwisestarting34560 of 69120
providerOff
provider == 'off'

Hand the pending sign-in only to the browser that started it

#IfThen
1no pending sign-in for this browser: a replay in another browser, a forged callback, or a spent oneRefuse a callback with no pending sign-in for this browser
2the pending sign-in is taken and handed to the method's callbackTurn the method's answer into the step's completion

The first matching rule wins.

Exact conditions
#ConditionGoes toSituations
1notTheStartingBrowserbad_request.transaction_mismatch23040 of 34560
2otherwisecompletingCallback11520 of 34560
notTheStartingBrowser
arrival != 'sameBrowser'

Turn the method's answer into the step's completion

#IfThen
1the person cancelled or declined at the providerShow the provider-error page for a cancel at the provider
2the provider answered another errorShow the provider-error page for an error the provider answered
3the provider's answer failed a check, or the provider could not be reachedRefuse an answer of the provider that fails a check
4the method broke the contract; its error is logged for the operatorRefuse the answer of a method that broke the contract
5the identity names no issuer: its sub could not be told apart from another issuer'sRefuse an answer of the provider that fails a check
6an identity and its attributesLook the credential up by (realm, provider type, sub) alone

The first matching rule wins.

Exact conditions
#ConditionGoes toSituations
1personDeclinedprovider_error.declined2304 of 11520
2providerErroredprovider_error.provider_error2304 of 11520
3checkFailedbad_request.invalid_response2304 of 11520
4methodFaultbad_request.method_fault2304 of 11520
5issuerMissingbad_request.invalid_response768 of 11520
6otherwiselookingUpCredential1536 of 11520
personDeclined
completion == 'declined'
providerErrored
completion == 'providerError'
checkFailed
completion == 'failed'
methodFault
completion == 'fault'
issuerMissing
issuer == 'missing'

Decide what the credential found allows

#IfThen
1the credential was created under another issuer: a sub is unique only within its issuer (OIDC Core §5.7), so the old credential never signs in under the new oneRefuse a known sub under another issuer
2the credential is disabledRefuse a disabled credential
3the credential's user, and whether the account counts as verified, as its creation recordedHand the account to the sign-in engine as proved by the upstream step
4an unknown identity, in a realm that does not allow sign-upRefuse an unknown identity in a realm that does not allow sign-up
5an unknown identity the provider's verified switch leaves unverified, with no address for a code or on the pages, which show no code step: it could never sign in, so nothing is writtenRefuse a new account nobody could verify
6an unknown identity, in a realm that allows sign-upCreate the user, the credential and the attributes in one transaction

The first matching rule wins.

Exact conditions
#ConditionGoes toSituations
1knownCredential and issuerDiffersbad_request.issuer_mismatch384 of 1536
2knownCredential and credentialDisabledbad_request.credential_disabled192 of 1536
3knownCredentialcompletingStep192 of 1536
4signUpOffbad_request.sign_up_disabled384 of 1536
5cannotVerifybad_request.verification_unavailable144 of 1536
6otherwisecreatingAccount240 of 1536
knownCredential
credential == 'known'
issuerDiffers
issuer == 'differs'
cannotVerify
vouching == 'off' and (address == 'none' or mode == 'pages')
credentialDisabled
credentialState == 'disabled'
signUpOff
signUp == 'off'

Go on as the engine's round says

#IfThen
1the engine refuses the proof, and audits whyRefuse a proof the engine refuses
2a further step the pages cannot show: the sign-in fails closedRefuse a sign-in the pages cannot finish
3a further step (the passkey, a code): the ceremony page shows itSend the browser to the ceremony page for the next step
4signed in: the pages start the authorization request again under the new sessionSend the signed-in browser back to the authorization request
5signed in: the interaction finishes the authorization request as the Interaction API doesSend the authorization code to the client

The first matching rule wins.

Exact conditions
#ConditionGoes toSituations
1engineRefusesbad_request.refused216 of 432
2needsFurtherStep and pagesModebad_request.step_not_available64 of 432
3needsFurtherStepstep_continues.ceremony_page80 of 432
4pagesModesigned_in.authorization_request32 of 432
5otherwisecode_issued40 of 432
engineRefuses
engineAnswer == 'refuses'
needsFurtherStep
furtherStep != 'none'
pagesMode
mode == 'pages'

Every way it can end

It ends withWhenWhat happens
Send the authorization code to the client
redirect with code + state + iss
The realm has the provider on, hidden or not, the provider's method starts and answers where the browser goes, the browser that started brings the flow cookie the pending sign-in is bound to, the method proved an identity, its assurance capped at low, a credential of the provider's type holds the identity's sub in the realm, the identity's issuer (the ID token's iss) is the one the credential was created under, credentialState is active, the provider's verified switch is on: an account it creates counts as verified, the provider gave an email, the realm's sign-up switch (accounts.allowSignUp) is on, the realm asks nothing more of the account, the engine takes the proof, INTERACTION_UI_ENABLED is on: the Interaction API's ceremony page shows any further step
  1. Wait for a person who chooses an upstream provider: the person picks a provider: the upstream alternative of the first sign-in step (Interaction API), or a provider link of /ui/login, which sends the browser back to the authorization request with qauth_upstream
  2. Find the provider among the realm's: the interaction starts the provider in its first round, recorded by the engine
  3. Ask the provider's method to start: start() answers
  4. Keep the pending sign-in, bound to the interaction and the flow cookie: the waiting page (a meta refresh, a link for a browser that does not follow it, and the way back) sends the browser to the provider; through the Interaction API, the answer's location does
  5. Wait while the person is at the provider: the provider sends the browser to /upstream/<type>/callback (a form_post is bounced to the GET route)
  6. Hand the pending sign-in only to the browser that started it: the pending sign-in is taken and handed to the method's callback
  7. Turn the method's answer into the step's completion: an identity and its attributes
  8. Look the credential up by (realm, provider type, sub) alone: one credential or none
  9. Decide what the credential found allows: the credential's user, and whether the account counts as verified, as its creation recorded
  10. Hand the account to the sign-in engine as proved by the upstream step: the engine answers its round
  11. Go on as the engine's round says: signed in: the interaction finishes the authorization request as the Interaction API does
Send the signed-in browser back to the authorization request
no redirect, page shown
The realm has the provider on, hidden or not, the provider's method starts and answers where the browser goes, the browser that started brings the flow cookie the pending sign-in is bound to, the method proved an identity, its assurance capped at low, a credential of the provider's type holds the identity's sub in the realm, the identity's issuer (the ID token's iss) is the one the credential was created under, credentialState is active, the provider's verified switch is on: an account it creates counts as verified, the provider gave an email, the realm's sign-up switch (accounts.allowSignUp) is on, the realm asks nothing more of the account, the engine takes the proof, INTERACTION_UI_ENABLED is off: the server-rendered pages, which show no further step
  1. Wait for a person who chooses an upstream provider: the person picks a provider: the upstream alternative of the first sign-in step (Interaction API), or a provider link of /ui/login, which sends the browser back to the authorization request with qauth_upstream
  2. Find the provider among the realm's: the interaction starts the provider in its first round, recorded by the engine
  3. Ask the provider's method to start: start() answers
  4. Keep the pending sign-in, bound to the interaction and the flow cookie: the waiting page (a meta refresh, a link for a browser that does not follow it, and the way back) sends the browser to the provider; through the Interaction API, the answer's location does
  5. Wait while the person is at the provider: the provider sends the browser to /upstream/<type>/callback (a form_post is bounced to the GET route)
  6. Hand the pending sign-in only to the browser that started it: the pending sign-in is taken and handed to the method's callback
  7. Turn the method's answer into the step's completion: an identity and its attributes
  8. Look the credential up by (realm, provider type, sub) alone: one credential or none
  9. Decide what the credential found allows: the credential's user, and whether the account counts as verified, as its creation recorded
  10. Hand the account to the sign-in engine as proved by the upstream step: the engine answers its round
  11. Go on as the engine's round says: signed in: the pages start the authorization request again under the new session
Send the browser to the ceremony page for the next step
no redirect, page shown
The realm has the provider on, hidden or not, the provider's method starts and answers where the browser goes, the browser that started brings the flow cookie the pending sign-in is bound to, the method proved an identity, its assurance capped at low, a credential of the provider's type holds the identity's sub in the realm, the identity's issuer (the ID token's iss) is the one the credential was created under, credentialState is active, the provider's verified switch is on: an account it creates counts as verified, the provider gave an email, the realm's sign-up switch (accounts.allowSignUp) is on, the account holds a passkey, which ranks above an upstream sign-in, so the engine asks for it, the engine takes the proof, INTERACTION_UI_ENABLED is on: the Interaction API's ceremony page shows any further step
  1. Wait for a person who chooses an upstream provider: the person picks a provider: the upstream alternative of the first sign-in step (Interaction API), or a provider link of /ui/login, which sends the browser back to the authorization request with qauth_upstream
  2. Find the provider among the realm's: the interaction starts the provider in its first round, recorded by the engine
  3. Ask the provider's method to start: start() answers
  4. Keep the pending sign-in, bound to the interaction and the flow cookie: the waiting page (a meta refresh, a link for a browser that does not follow it, and the way back) sends the browser to the provider; through the Interaction API, the answer's location does
  5. Wait while the person is at the provider: the provider sends the browser to /upstream/<type>/callback (a form_post is bounced to the GET route)
  6. Hand the pending sign-in only to the browser that started it: the pending sign-in is taken and handed to the method's callback
  7. Turn the method's answer into the step's completion: an identity and its attributes
  8. Look the credential up by (realm, provider type, sub) alone: one credential or none
  9. Decide what the credential found allows: the credential's user, and whether the account counts as verified, as its creation recorded
  10. Hand the account to the sign-in engine as proved by the upstream step: the engine answers its round
  11. Go on as the engine's round says: a further step (the passkey, a code): the ceremony page shows it
Show the provider-error page for a cancel at the provider
no redirect, page shown
The realm has the provider on, hidden or not, the provider's method starts and answers where the browser goes, the browser that started brings the flow cookie the pending sign-in is bound to, the person cancelled or declined at the provider (error=access_denied), a credential of the provider's type holds the identity's sub in the realm, the identity's issuer (the ID token's iss) is the one the credential was created under, credentialState is active, the provider's verified switch is on: an account it creates counts as verified, the provider gave an email, the realm's sign-up switch (accounts.allowSignUp) is on, the realm asks nothing more of the account, the engine takes the proof, INTERACTION_UI_ENABLED is off: the server-rendered pages, which show no further step
  1. Wait for a person who chooses an upstream provider: the person picks a provider: the upstream alternative of the first sign-in step (Interaction API), or a provider link of /ui/login, which sends the browser back to the authorization request with qauth_upstream
  2. Find the provider among the realm's: the interaction starts the provider in its first round, recorded by the engine
  3. Ask the provider's method to start: start() answers
  4. Keep the pending sign-in, bound to the interaction and the flow cookie: the waiting page (a meta refresh, a link for a browser that does not follow it, and the way back) sends the browser to the provider; through the Interaction API, the answer's location does
  5. Wait while the person is at the provider: the provider sends the browser to /upstream/<type>/callback (a form_post is bounced to the GET route)
  6. Hand the pending sign-in only to the browser that started it: the pending sign-in is taken and handed to the method's callback
  7. Turn the method's answer into the step's completion: the person cancelled or declined at the provider
Show the provider-error page for an error the provider answered
no redirect, page shown
The realm has the provider on, hidden or not, the provider's method starts and answers where the browser goes, the browser that started brings the flow cookie the pending sign-in is bound to, the provider answered another error, a credential of the provider's type holds the identity's sub in the realm, the identity's issuer (the ID token's iss) is the one the credential was created under, credentialState is active, the provider's verified switch is on: an account it creates counts as verified, the provider gave an email, the realm's sign-up switch (accounts.allowSignUp) is on, the realm asks nothing more of the account, the engine takes the proof, INTERACTION_UI_ENABLED is off: the server-rendered pages, which show no further step
  1. Wait for a person who chooses an upstream provider: the person picks a provider: the upstream alternative of the first sign-in step (Interaction API), or a provider link of /ui/login, which sends the browser back to the authorization request with qauth_upstream
  2. Find the provider among the realm's: the interaction starts the provider in its first round, recorded by the engine
  3. Ask the provider's method to start: start() answers
  4. Keep the pending sign-in, bound to the interaction and the flow cookie: the waiting page (a meta refresh, a link for a browser that does not follow it, and the way back) sends the browser to the provider; through the Interaction API, the answer's location does
  5. Wait while the person is at the provider: the provider sends the browser to /upstream/<type>/callback (a form_post is bounced to the GET route)
  6. Hand the pending sign-in only to the browser that started it: the pending sign-in is taken and handed to the method's callback
  7. Turn the method's answer into the step's completion: the provider answered another error
Refuse a provider that is off or cannot start
no redirect, page shown
The realm has no provider of that type, or has it off, the provider's method starts and answers where the browser goes, the browser that started brings the flow cookie the pending sign-in is bound to, the method proved an identity, its assurance capped at low, a credential of the provider's type holds the identity's sub in the realm, the identity's issuer (the ID token's iss) is the one the credential was created under, credentialState is active, the provider's verified switch is on: an account it creates counts as verified, the provider gave an email, the realm's sign-up switch (accounts.allowSignUp) is on, the realm asks nothing more of the account, the engine takes the proof, INTERACTION_UI_ENABLED is off: the server-rendered pages, which show no further step
  1. Wait for a person who chooses an upstream provider: the person picks a provider: the upstream alternative of the first sign-in step (Interaction API), or a provider link of /ui/login, which sends the browser back to the authorization request with qauth_upstream
  2. Find the provider among the realm's: the realm has no such provider on
Refuse a callback with no pending sign-in for this browser
no redirect, page shown
The realm has the provider on, hidden or not, the provider's method starts and answers where the browser goes, a browser with no such flow cookie: a callback replayed in another browser, forwarded or forged, the method proved an identity, its assurance capped at low, a credential of the provider's type holds the identity's sub in the realm, the identity's issuer (the ID token's iss) is the one the credential was created under, credentialState is active, the provider's verified switch is on: an account it creates counts as verified, the provider gave an email, the realm's sign-up switch (accounts.allowSignUp) is on, the realm asks nothing more of the account, the engine takes the proof, INTERACTION_UI_ENABLED is off: the server-rendered pages, which show no further step
  1. Wait for a person who chooses an upstream provider: the person picks a provider: the upstream alternative of the first sign-in step (Interaction API), or a provider link of /ui/login, which sends the browser back to the authorization request with qauth_upstream
  2. Find the provider among the realm's: the interaction starts the provider in its first round, recorded by the engine
  3. Ask the provider's method to start: start() answers
  4. Keep the pending sign-in, bound to the interaction and the flow cookie: the waiting page (a meta refresh, a link for a browser that does not follow it, and the way back) sends the browser to the provider; through the Interaction API, the answer's location does
  5. Wait while the person is at the provider: the provider sends the browser to /upstream/<type>/callback (a form_post is bounced to the GET route)
  6. Hand the pending sign-in only to the browser that started it: no pending sign-in for this browser: a replay in another browser, a forged callback, or a spent one
Refuse an answer of the provider that fails a check
no redirect, page shown
The realm has the provider on, hidden or not, the provider's method starts and answers where the browser goes, the browser that started brings the flow cookie the pending sign-in is bound to, the method proved an identity, its assurance capped at low, a credential of the provider's type holds the identity's sub in the realm, the identity names no issuer (rawClaims.iss): an AuthMethod plugin that breaks the contract, credentialState is active, the provider's verified switch is on: an account it creates counts as verified, the provider gave an email, the realm's sign-up switch (accounts.allowSignUp) is on, the realm asks nothing more of the account, the engine takes the proof, INTERACTION_UI_ENABLED is off: the server-rendered pages, which show no further step
  1. Wait for a person who chooses an upstream provider: the person picks a provider: the upstream alternative of the first sign-in step (Interaction API), or a provider link of /ui/login, which sends the browser back to the authorization request with qauth_upstream
  2. Find the provider among the realm's: the interaction starts the provider in its first round, recorded by the engine
  3. Ask the provider's method to start: start() answers
  4. Keep the pending sign-in, bound to the interaction and the flow cookie: the waiting page (a meta refresh, a link for a browser that does not follow it, and the way back) sends the browser to the provider; through the Interaction API, the answer's location does
  5. Wait while the person is at the provider: the provider sends the browser to /upstream/<type>/callback (a form_post is bounced to the GET route)
  6. Hand the pending sign-in only to the browser that started it: the pending sign-in is taken and handed to the method's callback
  7. Turn the method's answer into the step's completion: the identity names no issuer: its sub could not be told apart from another issuer's
Refuse the answer of a method that broke the contract
no redirect, page shown
The realm has the provider on, hidden or not, the provider's method starts and answers where the browser goes, the browser that started brings the flow cookie the pending sign-in is bound to, the method broke the AuthMethod contract, a credential of the provider's type holds the identity's sub in the realm, the identity's issuer (the ID token's iss) is the one the credential was created under, credentialState is active, the provider's verified switch is on: an account it creates counts as verified, the provider gave an email, the realm's sign-up switch (accounts.allowSignUp) is on, the realm asks nothing more of the account, the engine takes the proof, INTERACTION_UI_ENABLED is off: the server-rendered pages, which show no further step
  1. Wait for a person who chooses an upstream provider: the person picks a provider: the upstream alternative of the first sign-in step (Interaction API), or a provider link of /ui/login, which sends the browser back to the authorization request with qauth_upstream
  2. Find the provider among the realm's: the interaction starts the provider in its first round, recorded by the engine
  3. Ask the provider's method to start: start() answers
  4. Keep the pending sign-in, bound to the interaction and the flow cookie: the waiting page (a meta refresh, a link for a browser that does not follow it, and the way back) sends the browser to the provider; through the Interaction API, the answer's location does
  5. Wait while the person is at the provider: the provider sends the browser to /upstream/<type>/callback (a form_post is bounced to the GET route)
  6. Hand the pending sign-in only to the browser that started it: the pending sign-in is taken and handed to the method's callback
  7. Turn the method's answer into the step's completion: the method broke the contract; its error is logged for the operator
Refuse a known sub under another issuer
no redirect, page shown
The realm has the provider on, hidden or not, the provider's method starts and answers where the browser goes, the browser that started brings the flow cookie the pending sign-in is bound to, the method proved an identity, its assurance capped at low, a credential of the provider's type holds the identity's sub in the realm, it is another issuer: the provider was re-pointed, or another issuer reuses the sub, credentialState is active, the provider's verified switch is on: an account it creates counts as verified, the provider gave an email, the realm's sign-up switch (accounts.allowSignUp) is on, the realm asks nothing more of the account, the engine takes the proof, INTERACTION_UI_ENABLED is off: the server-rendered pages, which show no further step
  1. Wait for a person who chooses an upstream provider: the person picks a provider: the upstream alternative of the first sign-in step (Interaction API), or a provider link of /ui/login, which sends the browser back to the authorization request with qauth_upstream
  2. Find the provider among the realm's: the interaction starts the provider in its first round, recorded by the engine
  3. Ask the provider's method to start: start() answers
  4. Keep the pending sign-in, bound to the interaction and the flow cookie: the waiting page (a meta refresh, a link for a browser that does not follow it, and the way back) sends the browser to the provider; through the Interaction API, the answer's location does
  5. Wait while the person is at the provider: the provider sends the browser to /upstream/<type>/callback (a form_post is bounced to the GET route)
  6. Hand the pending sign-in only to the browser that started it: the pending sign-in is taken and handed to the method's callback
  7. Turn the method's answer into the step's completion: an identity and its attributes
  8. Look the credential up by (realm, provider type, sub) alone: one credential or none
  9. Decide what the credential found allows: the credential was created under another issuer: a sub is unique only within its issuer (OIDC Core §5.7), so the old credential never signs in under the new one
Refuse a disabled credential
no redirect, page shown
The realm has the provider on, hidden or not, the provider's method starts and answers where the browser goes, the browser that started brings the flow cookie the pending sign-in is bound to, the method proved an identity, its assurance capped at low, a credential of the provider's type holds the identity's sub in the realm, the identity's issuer (the ID token's iss) is the one the credential was created under, credentialState is disabled, the provider's verified switch is on: an account it creates counts as verified, the provider gave an email, the realm's sign-up switch (accounts.allowSignUp) is on, the realm asks nothing more of the account, the engine takes the proof, INTERACTION_UI_ENABLED is off: the server-rendered pages, which show no further step
  1. Wait for a person who chooses an upstream provider: the person picks a provider: the upstream alternative of the first sign-in step (Interaction API), or a provider link of /ui/login, which sends the browser back to the authorization request with qauth_upstream
  2. Find the provider among the realm's: the interaction starts the provider in its first round, recorded by the engine
  3. Ask the provider's method to start: start() answers
  4. Keep the pending sign-in, bound to the interaction and the flow cookie: the waiting page (a meta refresh, a link for a browser that does not follow it, and the way back) sends the browser to the provider; through the Interaction API, the answer's location does
  5. Wait while the person is at the provider: the provider sends the browser to /upstream/<type>/callback (a form_post is bounced to the GET route)
  6. Hand the pending sign-in only to the browser that started it: the pending sign-in is taken and handed to the method's callback
  7. Turn the method's answer into the step's completion: an identity and its attributes
  8. Look the credential up by (realm, provider type, sub) alone: one credential or none
  9. Decide what the credential found allows: the credential is disabled
Refuse an unknown identity in a realm that does not allow sign-up
no redirect, page shown
The realm has the provider on, hidden or not, the provider's method starts and answers where the browser goes, the browser that started brings the flow cookie the pending sign-in is bound to, the method proved an identity, its assurance capped at low, none does, whatever another account holds as its email, the identity's issuer (the ID token's iss) is the one the credential was created under, credentialState is active, the provider's verified switch is on: an account it creates counts as verified, the provider gave an email, it is off, the default, the realm asks nothing more of the account, the engine takes the proof, INTERACTION_UI_ENABLED is off: the server-rendered pages, which show no further step
  1. Wait for a person who chooses an upstream provider: the person picks a provider: the upstream alternative of the first sign-in step (Interaction API), or a provider link of /ui/login, which sends the browser back to the authorization request with qauth_upstream
  2. Find the provider among the realm's: the interaction starts the provider in its first round, recorded by the engine
  3. Ask the provider's method to start: start() answers
  4. Keep the pending sign-in, bound to the interaction and the flow cookie: the waiting page (a meta refresh, a link for a browser that does not follow it, and the way back) sends the browser to the provider; through the Interaction API, the answer's location does
  5. Wait while the person is at the provider: the provider sends the browser to /upstream/<type>/callback (a form_post is bounced to the GET route)
  6. Hand the pending sign-in only to the browser that started it: the pending sign-in is taken and handed to the method's callback
  7. Turn the method's answer into the step's completion: an identity and its attributes
  8. Look the credential up by (realm, provider type, sub) alone: one credential or none
  9. Decide what the credential found allows: an unknown identity, in a realm that does not allow sign-up
Refuse a new account nobody could verify
no redirect, page shown
The realm has the provider on, hidden or not, the provider's method starts and answers where the browser goes, the browser that started brings the flow cookie the pending sign-in is bound to, the method proved an identity, its assurance capped at low, none does, whatever another account holds as its email, the identity's issuer (the ID token's iss) is the one the credential was created under, credentialState is active, it is off: QAuth verifies the address of an account it creates with an emailed code, the provider gave an email, the realm's sign-up switch (accounts.allowSignUp) is on, the realm asks nothing more of the account, the engine takes the proof, INTERACTION_UI_ENABLED is off: the server-rendered pages, which show no further step
  1. Wait for a person who chooses an upstream provider: the person picks a provider: the upstream alternative of the first sign-in step (Interaction API), or a provider link of /ui/login, which sends the browser back to the authorization request with qauth_upstream
  2. Find the provider among the realm's: the interaction starts the provider in its first round, recorded by the engine
  3. Ask the provider's method to start: start() answers
  4. Keep the pending sign-in, bound to the interaction and the flow cookie: the waiting page (a meta refresh, a link for a browser that does not follow it, and the way back) sends the browser to the provider; through the Interaction API, the answer's location does
  5. Wait while the person is at the provider: the provider sends the browser to /upstream/<type>/callback (a form_post is bounced to the GET route)
  6. Hand the pending sign-in only to the browser that started it: the pending sign-in is taken and handed to the method's callback
  7. Turn the method's answer into the step's completion: an identity and its attributes
  8. Look the credential up by (realm, provider type, sub) alone: one credential or none
  9. Decide what the credential found allows: an unknown identity the provider's verified switch leaves unverified, with no address for a code or on the pages, which show no code step: it could never sign in, so nothing is written
Refuse a proof the engine refuses
no redirect, page shown
The realm has the provider on, hidden or not, the provider's method starts and answers where the browser goes, the browser that started brings the flow cookie the pending sign-in is bound to, the method proved an identity, its assurance capped at low, a credential of the provider's type holds the identity's sub in the realm, the identity's issuer (the ID token's iss) is the one the credential was created under, credentialState is active, the provider's verified switch is on: an account it creates counts as verified, the provider gave an email, the realm's sign-up switch (accounts.allowSignUp) is on, the realm asks nothing more of the account, it refuses it: the account is disabled, or the interaction moved on, INTERACTION_UI_ENABLED is off: the server-rendered pages, which show no further step
  1. Wait for a person who chooses an upstream provider: the person picks a provider: the upstream alternative of the first sign-in step (Interaction API), or a provider link of /ui/login, which sends the browser back to the authorization request with qauth_upstream
  2. Find the provider among the realm's: the interaction starts the provider in its first round, recorded by the engine
  3. Ask the provider's method to start: start() answers
  4. Keep the pending sign-in, bound to the interaction and the flow cookie: the waiting page (a meta refresh, a link for a browser that does not follow it, and the way back) sends the browser to the provider; through the Interaction API, the answer's location does
  5. Wait while the person is at the provider: the provider sends the browser to /upstream/<type>/callback (a form_post is bounced to the GET route)
  6. Hand the pending sign-in only to the browser that started it: the pending sign-in is taken and handed to the method's callback
  7. Turn the method's answer into the step's completion: an identity and its attributes
  8. Look the credential up by (realm, provider type, sub) alone: one credential or none
  9. Decide what the credential found allows: the credential's user, and whether the account counts as verified, as its creation recorded
  10. Hand the account to the sign-in engine as proved by the upstream step: the engine answers its round
  11. Go on as the engine's round says: the engine refuses the proof, and audits why
Refuse a sign-in the pages cannot finish
no redirect, page shown
The realm has the provider on, hidden or not, the provider's method starts and answers where the browser goes, the browser that started brings the flow cookie the pending sign-in is bound to, the method proved an identity, its assurance capped at low, a credential of the provider's type holds the identity's sub in the realm, the identity's issuer (the ID token's iss) is the one the credential was created under, credentialState is active, the provider's verified switch is on: an account it creates counts as verified, the provider gave an email, the realm's sign-up switch (accounts.allowSignUp) is on, the account holds a passkey, which ranks above an upstream sign-in, so the engine asks for it, the engine takes the proof, INTERACTION_UI_ENABLED is off: the server-rendered pages, which show no further step
  1. Wait for a person who chooses an upstream provider: the person picks a provider: the upstream alternative of the first sign-in step (Interaction API), or a provider link of /ui/login, which sends the browser back to the authorization request with qauth_upstream
  2. Find the provider among the realm's: the interaction starts the provider in its first round, recorded by the engine
  3. Ask the provider's method to start: start() answers
  4. Keep the pending sign-in, bound to the interaction and the flow cookie: the waiting page (a meta refresh, a link for a browser that does not follow it, and the way back) sends the browser to the provider; through the Interaction API, the answer's location does
  5. Wait while the person is at the provider: the provider sends the browser to /upstream/<type>/callback (a form_post is bounced to the GET route)
  6. Hand the pending sign-in only to the browser that started it: the pending sign-in is taken and handed to the method's callback
  7. Turn the method's answer into the step's completion: an identity and its attributes
  8. Look the credential up by (realm, provider type, sub) alone: one credential or none
  9. Decide what the credential found allows: the credential's user, and whether the account counts as verified, as its creation recorded
  10. Hand the account to the sign-in engine as proved by the upstream step: the engine answers its round
  11. Go on as the engine's round says: a further step the pages cannot show: the sign-in fails closed
Refuse the callback when a store cannot answer
no redirect, page shown
The realm has the provider on, hidden or not, the provider's method starts and answers where the browser goes, the browser that started brings the flow cookie the pending sign-in is bound to, the method proved an identity, its assurance capped at low, a credential of the provider's type holds the identity's sub in the realm, the identity's issuer (the ID token's iss) is the one the credential was created under, credentialState is active, the provider's verified switch is on: an account it creates counts as verified, the provider gave an email, the realm's sign-up switch (accounts.allowSignUp) is on, the realm asks nothing more of the account, the engine takes the proof, INTERACTION_UI_ENABLED is off: the server-rendered pages, which show no further step
  1. Wait for a person who chooses an upstream provider: the person picks a provider: the upstream alternative of the first sign-in step (Interaction API), or a provider link of /ui/login, which sends the browser back to the authorization request with qauth_upstream
  2. Find the provider among the realm's: the interaction starts the provider in its first round, recorded by the engine
  3. Ask the provider's method to start: start() answers
  4. Keep the pending sign-in, bound to the interaction and the flow cookie: the waiting page (a meta refresh, a link for a browser that does not follow it, and the way back) sends the browser to the provider; through the Interaction API, the answer's location does
  5. Wait while the person is at the provider: the provider sends the browser to /upstream/<type>/callback (a form_post is bounced to the GET route)
  6. Hand the pending sign-in only to the browser that started it: the pending sign-in is taken and handed to the method's callback
  7. Turn the method's answer into the step's completion: an identity and its attributes
  8. Look the credential up by (realm, provider type, sub) alone: one credential or none
  9. Decide what the credential found allows: the credential's user, and whether the account counts as verified, as its creation recorded
  10. Hand the account to the sign-in engine as proved by the upstream step: it fails, because the interaction store or the failure ladder cannot answer
Fail on a database error
no redirect, page shown
The realm has the provider on, hidden or not, the provider's method starts and answers where the browser goes, the browser that started brings the flow cookie the pending sign-in is bound to, the method proved an identity, its assurance capped at low, a credential of the provider's type holds the identity's sub in the realm, the identity's issuer (the ID token's iss) is the one the credential was created under, credentialState is active, the provider's verified switch is on: an account it creates counts as verified, the provider gave an email, the realm's sign-up switch (accounts.allowSignUp) is on, the realm asks nothing more of the account, the engine takes the proof, INTERACTION_UI_ENABLED is off: the server-rendered pages, which show no further step
  1. Wait for a person who chooses an upstream provider: the person picks a provider: the upstream alternative of the first sign-in step (Interaction API), or a provider link of /ui/login, which sends the browser back to the authorization request with qauth_upstream
  2. Find the provider among the realm's: the interaction starts the provider in its first round, recorded by the engine
  3. Ask the provider's method to start: start() answers
  4. Keep the pending sign-in, bound to the interaction and the flow cookie: the waiting page (a meta refresh, a link for a browser that does not follow it, and the way back) sends the browser to the provider; through the Interaction API, the answer's location does
  5. Wait while the person is at the provider: the provider sends the browser to /upstream/<type>/callback (a form_post is bounced to the GET route)
  6. Hand the pending sign-in only to the browser that started it: the pending sign-in is taken and handed to the method's callback
  7. Turn the method's answer into the step's completion: an identity and its attributes
  8. Look the credential up by (realm, provider type, sub) alone: it fails, because the database fails
The person did not come back
user leaves the page
The realm has the provider on, hidden or not, the provider's method starts and answers where the browser goes, the browser that started brings the flow cookie the pending sign-in is bound to, the method proved an identity, its assurance capped at low, a credential of the provider's type holds the identity's sub in the realm, the identity's issuer (the ID token's iss) is the one the credential was created under, credentialState is active, the provider's verified switch is on: an account it creates counts as verified, the provider gave an email, the realm's sign-up switch (accounts.allowSignUp) is on, the realm asks nothing more of the account, the engine takes the proof, INTERACTION_UI_ENABLED is off: the server-rendered pages, which show no further step
  1. Wait for a person who chooses an upstream provider: the person picks a provider: the upstream alternative of the first sign-in step (Interaction API), or a provider link of /ui/login, which sends the browser back to the authorization request with qauth_upstream
  2. Find the provider among the realm's: the interaction starts the provider in its first round, recorded by the engine
  3. Ask the provider's method to start: start() answers
  4. Keep the pending sign-in, bound to the interaction and the flow cookie: the waiting page (a meta refresh, a link for a browser that does not follow it, and the way back) sends the browser to the provider; through the Interaction API, the answer's location does
  5. Wait while the person is at the provider: the person never comes back

Each row is also a generated test: set the starting situation, send the events, expect the ending.

What is guaranteed

These hold in all 432384 situations the model can reach, from 69120 different starting situations.

Exact rules
IdNever
only-the-starting-browser-signs-insignedIn and notTheStartingBrowser
a-foreign-callback-spends-nothingpendingTaken and notTheStartingBrowser
no-account-without-sign-upaccountCreated and signUpOff
no-account-that-could-never-sign-inaccountCreated and cannotVerify
no-subject-without-its-issuer(accountCreated or signedIn) and issuerMissing
no-account-for-a-known-identityaccountCreated and knownCredential
an-issuer-change-never-signs-insignedIn and knownCredential and issuerDiffers
a-passkey-is-never-skippedsignedIn and furtherStep == 'passkey'
a-cancel-is-not-a-failurestate == 'provider_error.declined' and not personDeclined

Reference

All transitions (35)
FromEventGuardToEffectsTaken
idlePICKresolvingProvideryes
idleROUTEresolvingProvideryes
resolvingProviderrule 1providerOffbad_request.upstream_unavailableyes
resolvingProviderrule 2startingyes
startingDONEkeepingPendingyes
startingFAIL.rejectedbad_request.upstream_unavailableyes
keepingPendingDONEatProvideryes
keepingPendingFAIL.storebad_request.upstream_unavailableyes
atProviderBROWSER_RETURNScheckingPendingyes
atProviderABANDONabandonedyes
checkingPendingrule 1notTheStartingBrowserbad_request.transaction_mismatchyes
checkingPendingrule 2completingCallbacktakePendingyes
completingCallbackrule 1personDeclinedprovider_error.declinedyes
completingCallbackrule 2providerErroredprovider_error.provider_erroryes
completingCallbackrule 3checkFailedbad_request.invalid_responseyes
completingCallbackrule 4methodFaultbad_request.method_faultyes
completingCallbackrule 5issuerMissingbad_request.invalid_responseyes
completingCallbackrule 6lookingUpCredentialyes
lookingUpCredentialDONEcheckingCredentialyes
lookingUpCredentialFAIL.serverserver_error.storeyes
checkingCredentialrule 1knownCredential and issuerDiffersbad_request.issuer_mismatchyes
checkingCredentialrule 2knownCredential and credentialDisabledbad_request.credential_disabledyes
checkingCredentialrule 3knownCredentialcompletingStepyes
checkingCredentialrule 4signUpOffbad_request.sign_up_disabledyes
checkingCredentialrule 5cannotVerifybad_request.verification_unavailableyes
checkingCredentialrule 6creatingAccountyes
creatingAccountDONEcompletingStepcreateAccountyes
creatingAccountFAIL.serverserver_error.storeyes
completingStepDONEdecidingRoundyes
completingStepFAIL.unavailablebad_request.unavailableyes
decidingRoundrule 1engineRefusesbad_request.refusedyes
decidingRoundrule 2needsFurtherStep and pagesModebad_request.step_not_availableyes
decidingRoundrule 3needsFurtherStepstep_continues.ceremony_pageyes
decidingRoundrule 4pagesModesigned_in.authorization_requestsignInyes
decidingRoundrule 5code_issuedsignInyes