Beta

Create a tenant

A new tenant starts with its own users, OAuth settings, audit history and logs. You are its first Tenant Admin.

BTL Admin

OPENID CONNECT · LAB

Receive every sign-in error your tenant can return, and fail closed

Produce access_denied, login_required, consent_required and account_selection_required at your callback, handle each one, and refuse to sign anyone in from a response without an ID token.

ReadyUses your lab tenant

The lesson

Builds on: The authentication request.

New to the labs? Start with the lab toolkit and the shared cast and names every lab uses.

Your progress

Press Start before you begin. Only events your tenant records after that count, in the order below. Checking reads your tenant's Audit, so you need Audit read access in it.

  1. Ava denies the collage app at the consent page

    Recorded as oauth.authorize rejected (access_denied) for lab-collage about [email protected].

  2. A silent request finds no one signed in

    Recorded as oauth.authorize rejected (login_required) for lab-collage.

  3. A silent request needs consent that cannot be asked for

    Recorded as oauth.authorize rejected (consent_required) for lab-collage about [email protected].

  4. A silent request hints at another account

    Recorded as oauth.authorize rejected (account_selection_required) for lab-collage about [email protected].

  5. UserInfo refuses the token from a response without an ID token

    Recorded as oidc.userinfo rejected (insufficient_scope) for lab-collage.

Setup

  1. You need lab-collage, Ava, Ben and the signin_url and exchange helpers from the authentication request lab. lab-collage uses consent mode remember.

  2. Keep btl-lab callback running in a second terminal. It also prints error and error_description when a callback carries them.

  3. Press Start.

Walkthrough

  1. access_denied. Run signin_url 'openid%20profile%20email%20photos.read' and open it in a private window. Sign in as Ava and select Deny access on the consent page. The listener prints error=access_denied, your state and iss.

Handle it exactly like a success up to the point where a code would be used: confirm that state matches your attempt and iss equals $ISSUER, then mark the attempt finished so it cannot be resumed.

Why it matters: errors return through the same callback and get the same state and issuer checks as a code.

  1. Write the message the person sees. error_description is developer text from the provider, so never show it as your own words or insert it into a page as markup. Write your own, for example: "We couldn't sign you in with your Lab Photos account. Try again, or sign in another way." A denial Ava chose deserves a calmer message than an unexpected failure.

  1. login_required. Open a fresh private window with no tenant session and use signin_url 'openid' '&prompt=none'. The listener prints error=login_required at once, without any page.

Why it matters: under prompt=none this is an answer to the question you asked ("is anyone signed in?"), not a failure. Your handler decides whether to show its own sign-in page or carry on without a signed-in person.

  1. Run the same URL without &prompt=none in that window. You see the tenant's sign-in page instead. An interactive request that came back with login_required would be surprising and worth investigating.

  1. consent_required. In OAuth > Clients, set lab-collage consent mode to always. In a window where Ava is signed in, run signin_url 'openid' '&prompt=none'. The listener prints error=consent_required: Ava would have to approve again, and the request asked for no pages.

Restore: set lab-collage consent mode back to remember now.

  1. account_selection_required. In the window where Ava is signed in, run signin_url 'openid' '&prompt=none&login_hint=ben%40example.com'. The listener prints error=account_selection_required: the hint names another account, and choosing one needs a page.

  1. Fail closed. Run signin_url 'profile%20email' (no openid), approve and run exchange '<code>'. The response succeeds, but id_token is absent. Your handler stops here. For completeness, see that the tempting fallback would not even work on this tenant:

curl -si "$ISSUER/oidc/userinfo" -H "Authorization: Bearer $TOKEN" | head -5

Expect 403 with Bearer error="insufficient_scope".

Why it matters: no validated ID token, no sign-in. A UserInfo fallback would turn a broken response into a sign-in, because UserInfo answers for whoever holds a suitable access token and says nothing about this attempt.

  1. Log the failures safely. Write one JSON line per failure with the stage, the error or failed check, the expected issuer and a correlation ID you generate. Your state is not a correlation ID, and nothing from the code, the tokens or the full callback URL belongs in the line:

jq -cn --arg e access_denied --arg i "$ISSUER" --arg c "$(openssl rand -hex 8)" \
  '{event: "sign_in.provider_error", message: "Sign-in ended with a provider error", stage: "callback", error: $e, expected_issuer: $i, correlation_id: $c}'

Break it

  1. A return address that is not registered. Run signin_url 'openid' and replace the encoded redirect_uri with https%3A%2F%2Fexample.com%2Fcb. The tenant shows its own error page and never redirects, because it cannot trust that address with an error or a code.

  1. An error callback that matches no attempt. Take the access_denied callback values from step 1 and present them again after the attempt is finished. Your handler finds no pending attempt in this browser and does nothing, exactly as for any other unexpected request to the callback.

Check your work

Press Check my progress. It looks for, in order: oauth.authorize rejected with access_denied, login_required, consent_required and account_selection_required for lab-collage, then oidc.userinfo rejected with insufficient_scope.

The unregistered return address in Break it never reaches Audit, because the tenant could not attribute it to a valid client and address. Find it in Logs as oauth.authorize rejected with invalid_redirect_uri. Audit also shows the two tenant.oauth.clients.update records from step 5.

Cleanup

Confirm that lab-collage consent mode is remember, and run unset TOKEN ID_TOKEN RESP.

Back to all labs

We value your privacy

We use cookies and similar technologies to enhance your browsing experience, and analytics to understand our traffic. By clicking "Allow All", you consent to optional analytics. Cookie Policy

The Lab