AUTH4D-236 First-party applications
The hosted login asks a player to allow an application after they sign in. A site you run yourself
does not need that step, so a browser application can be marked first-party. The setting is off by
default, and every application that existed before migration 0036_first_party_applications.sql is
off.
When the consent screen is skipped
The hosted login continues without the consent screen only when all of these hold:
- the application has
firstPartyon; - it is a browser application;
- the request’s
redirect_uriishttpson a public DNS name. Loopback and local hosts (localhost,*.localhost,127.0.0.0/8,::1), every IP address, single-label names, reserved names such as.local,.internaland.test,httpURIs and custom schemes do not qualify; - the redirect URI is not on the auth origin, so a guest-linking flow, which returns to the hosted login itself, is unchanged;
- the request does not have
prompt=consent.
Native and server applications, the device approval page (its confirm-the-code step guards against
device-code phishing), guest sign-in (GET /t/{tenantId}/guest/authorize) and guest upgrades are
unchanged.
When the screen is skipped, POST /oauth/authorize/continue records the grant exactly as if the
player had chosen Allow: the same consent row, with the union of the scopes already granted and
the requested scopes. prompt=none, consent revocation, refresh and the console consent views keep
working. If the grant adds scopes (a new player, new scopes, or consent that was revoked), the same
D1 batch writes a tenant audit event:
| Field | Value |
|---|---|
event |
oauth.consent.granted |
actor_id |
the player’s user id |
target_type |
client, with target_id the application id |
metadata |
{"consent_screen": "skipped", "scope_count": n} |
A returning player whose grant already covers the requested scopes adds no event. Grants made by choosing Allow on the consent screen are recorded as before, without this event.
prompt=consent
GET /oauth/authorize now accepts prompt=consent as well as prompt=none. It always shows the
consent screen, also for a first-party application. Other values and lists are still
invalid_request. Discovery advertises "prompt_values_supported": ["none", "consent"].
Login context
GET /t/{tenantId}/login/context?client_id=…&flow_id=… adds "skip_consent": true when the flow
meets every condition above. The key is absent otherwise, and without flow_id. The hosted login
then sends the approve decision itself and shows “Signing in to {application}…”. If that request
fails in transit, the page shows the error and a “Try again” button; any other failure shows the
expired-session screen, as the consent screen does. A player who already has a hosted-login session
also returns at once, so the consent screen’s “Use a different account” link is not offered; an app
that wants the player to choose can send prompt=consent.
Management API
PATCH /management/tenants/{tenantId}/clients/{clientId} accepts firstParty (boolean). Only
project owners and admins can change it; viewers get 403 insufficient_role. Game (native)
applications get 400 first_party_unsupported, with a detail for the firstParty field, and a
value that is not a boolean is 400 validation_failed. A server client answers
400 validation_failed: since AUTH4D-305 (repository file docs/api-spec/auth4d-305.md) its PATCH takes only displayName and
enabled.
{ "firstParty": true }
GET and PATCH on .../clients/{clientId} return firstParty: true or false for browser
applications and null for the others. A change writes the clients.first_party column and a
tenant audit event in one D1 batch. A PATCH that does not change the value writes nothing.
| Field | Value |
|---|---|
event |
management.client.first_party.updated |
actor_id |
the developer |
target_type |
client, with target_id the application id |
metadata |
{"client_type": "browser", "first_party": "enabled"} or "disabled" |
Console
The application screen of a browser application has a Consent screen section with a First-party application switch. Its help text says when consent is still shown. Viewers see the switch read-only.
Storage
0036_first_party_applications.sql adds clients.first_party (0 or 1, default 0). It is
additive; code deployed before the migration reads a missing column as off.
Tests
tests/integration/auth4d-236.first-party.test.ts: the skip with an audited consent row,prompt=noneafterwards, loopback andhttpredirect URIs,prompt=consent, applications that are not first-party, hosted guest sign-in, device approval, the redirect URI rule, and the management API (owners and admins, viewers refused, audit events, game applications refused).tests/e2e/auth4d-236.first-party-login.spec.tsandauth4d-236.first-party-console.spec.ts(tests/e2e/auth4d-236.playwright.config.ts): the hosted login without the consent screen, the retry path, and the console switch by keyboard, read-only for viewers, at 320px and 1280px.
Source: docs/api-spec/auth4d-236.md