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

Processing a login initiation request

All domains, identifiers, and values in these examples are fictional.

The portal's login initiation endpoint is registered, and the dashboard's tiles point at it. Now the portal's developers write the code behind /signin/start.

The request it handles looks official. It came from Lantern's dashboard, it names Lantern's identity provider, and it points at a real project. Nothing in it proves any of that.

Choosing the provider

The portal accepts iss only if it exactly matches the issuer identifier of a provider the portal is already configured to use, compared character for character, as Issuer, audience, and authorized party compared the iss claim. For Lantern's portal the list has one entry, https://idp.lantern.example. A request naming any other issuer, including the lookalike above, starts no sign-in. The portal shows its ordinary sign-in page instead, where the designer can begin in the usual way.

A match selects configuration the portal already holds for that provider: its authorization and token endpoints, its key set, and the portal's own client ID and credentials there. The portal never runs discovery for an issuer it has just read from a request. Provider discovery said that trust starts from an issuer the relying party chose, never one taken from a token or a callback, and a login initiation request is one more place where an issuer can be offered. Fetching configuration for an arbitrary issuer would let anyone choose where the designer signs in, and would let anyone make the portal's servers send requests to addresses of their choosing.

The matched issuer becomes the expected issuer of the pending sign-in, exactly as when someone chooses a provider on the portal's own page. If the portal supports several providers, the protections from Supporting several providers apply unchanged: the response and the ID token must come from the issuer this attempt expected.

login_hint needs less care, because it grants nothing. The portal can pass it to the provider in its authentication request, where, as Login hints explained, it only suggests an account. The provider still authenticates whoever is at the browser. The portal checks that the value is short and well formed before copying it into a URL, and drops it otherwise. A hint naming someone else's account cannot sign the designer in as that person. At most, the provider suggests the wrong account on its sign-in page.

Checking the target

target_link_uri is the parameter the specification singles out. The relying party must verify it, so that its login initiation endpoint cannot be used as an open redirector to other sites. Redirect URI validation described open redirects and why attackers value them: a link that begins with a trusted address and ends on the attacker's page. This one is especially convincing, because the designer would reach the attacker's page straight after a genuine sign-in, at the moment they have every reason to trust the next page.

The portal checks the target against the pages it is willing to show:

  • It parses the value with a standard URL parser and requires the scheme https and its own host, portal.lantern.example, compared exactly. A prefix check fails here for the same reasons it failed for redirect URIs. https://portal.lantern.example.attacker.example/ begins with the right characters and belongs to someone else.
  • It requires the path to be one of the portal's own pages, such as a project page, from a list or pattern the portal maintains.
  • If the target is missing or fails either check, it ignores the target and lands on the portal's home page after sign-in. The target was only a preference, so there is no need to refuse the whole sign-in.

The portal keeps the checked path with the pending sign-in on its own server, not in state, as Correlating requests and responses advised for return destinations. After the callback, it reads the destination from the pending sign-in, never from the callback itself.

Reaching the target is not the same as being allowed to see it. The autumn catalogue page still checks, as every project page does, that the signed-in designer belongs to that project. A link from the dashboard says where the designer wanted to go. The portal's own permissions decide what they find there.

Starting a fresh request

With a provider chosen and a destination checked, the portal does exactly what its own sign-in button does. It creates a pending sign-in for this browser:

Pending sign-in: demo-portal-signin-4
Initiating session: the current, not yet signed-in portal session
Expected issuer: https://idp.lantern.example
Redirect URI: https://portal.lantern.example/signin/callback
Nonce: demo-portal-nonce-4
PKCE verifier: held privately on the backend
Return destination: /projects/autumn-catalogue
Status: pending, with a limited lifetime

Then it redirects the browser to Lantern's identity provider with an ordinary authentication request:

https://idp.lantern.example/authorize
  ?response_type=code
  &client_id=lantern-portal
  &redirect_uri=https%3A%2F%2Fportal.lantern.example%2Fsignin%2Fcallback
  &scope=openid%20profile
  &state=demo-portal-signin-4
  &nonce=demo-portal-nonce-4
  &code_challenge=N746XYTHj5ctZ7qbJbjsq9xKqCn-uPhdKT7Qrm3clys
  &code_challenge_method=S256
  &login_hint=designer-42

Readable values help us follow the example. Real ones are fresh and unpredictable, and every one of them is created here, by the portal. The login initiation request supplied none of them, and if it had carried parameters named state or nonce, the portal would ignore them, because they would be values someone else chose. From here on the sign-in is an ordinary one. The callback must match this pending sign-in in this browser, and the ID token is validated as in Validating an ID token, nonce included.

Sometimes the designer already has a portal session from earlier in the day. The portal can then skip the round trip and go straight to the checked target, provided that session came from the issuer the request named and belongs to the account the hint names. Here the hint is a subject identifier, so the portal can compare it with the session's subject directly. If they differ, the portal starts a fresh sign-in rather than opening whichever account happens to be signed in. The login initiation request itself never changes who is signed in. Only a completed, validated sign-in like this one can do that.

One risk remains. Any website can send the designer's browser to the login initiation endpoint, and because Lantern's identity provider remembers the designer, the whole exchange can complete without showing a single page. The designer can end up signed in to the portal without having chosen to. That gives no one anything on its own. Combined with clickjacking, though, it matters: a page loads the portal inside an invisible frame and tricks the designer into clicking buttons they cannot see, acting in the portal with the designer's new session. The specification asks relying parties to defend against this. Browsers that withhold cookies from frames on other sites already make it harder, but the portal should not depend on that. The standard defense is a response header that forbids other sites from framing the portal's pages at all:

Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENY

Current browsers follow the first header, and the second covers older ones. The portal sends them with every page, including the login initiation endpoint and the sign-in callback.

Try it in the Lab

PUT IT INTO PRACTICE

Check your understanding

Try these questions before moving on. If an answer isn't right, use the feedback and try again.

0 of 2 answered correctly

Enable JavaScript to answer these questions and save progress in this browser.

QUESTION 1 OF 2A login initiation request names iss=https://idp.lantern-sso.example, which is not in the portal's configuration. What should the portal do?

QUESTION 2 OF 2The portal accepts any target_link_uri that begins with https://portal.lantern.example. A request arrives whose target is on the host portal.lantern.example.attacker.example. What happens?

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

Learn identity