Federated Login Errors (Browser Redirect Surface)
Surface: browser redirect — 302 Found / 303 See Other, no response body
Carrier: the error and ref query parameters on the console login URL
Not an RFC 7807 problem type — see How this differs from every other page in this reference
When an external identity-federation login fails, the browser is redirected to the ANKASecure console login screen carrying a machine-readable cause and a support reference:
HTTP/1.1 302 Found
Location: https://<edge-host>/admin/login?error=access_not_configured&ref=7c9e6679-7425-40de-944b-e07fc1f90ae7
Content-Length: 0
The console renders a human-readable explanation for the error code and displays the ref. Quote the ref when contacting an operator or support — it is the correlation identifier that ties the failure to the server-side log line and to the audit record.
How this differs from every other page in this reference
Every other error in this reference is an API error: a response body in RFC 7807 Problem Details form, served as application/problem+json, carrying type, title, status, detail, instance, correlationId and timestamp.
This surface is not an API response. It is a browser redirect, and it therefore has:
- no body —
Content-Length: 0 - no
type— the cause is theerrorquery parameter, not a URI - no
correlationId— the correlation identifier is therefquery parameter - no
statusbeyond the redirect itself —302(OIDC callback,initiate) or303 See Other(SAML ACS)
Consequently the "Programmatic Error Handling" example on the Error Reference Index, which dispatches on problemDetails.getType(), does not apply here. There is no problem document to dispatch on. A programmatic consumer never reaches this surface: it exists only for a top-level browser navigation.
Why 303 See Other on the SAML assertion consumer service
The SAML ACS is a POST endpoint (the IdP posts SAMLResponse as a form). A conforming client that receives a 302 on a POST may re-issue the request with the same method and body, which would re-post the assertion. 303 See Other requires the client to follow with GET, so the redirect is unambiguous. The OIDC callback and initiate are GET navigations and use 302.
The boundary rule — when a surface may redirect instead of returning a problem document
A surface may use the browser-redirect error projection if and only if all three conditions hold:
- the request is a top-level browser navigation that the browser renders as a document;
- the caller therefore cannot read a response body, so an interposing CDN or proxy may replace it;
- an equivalent typed RFC 7807 refusal remains available on a non-navigation surface for programmatic consumers.
Every non-navigation surface — fetch/XHR, the SDKs, the CLIs and service-to-service calls — keeps RFC 7807 unconditionally. The redirect is an additional projection of refusals that already exist as typed problem documents; it retires none of them and renumbers none of them.
Three federation legs meet all three conditions and therefore redirect:
| Leg | Method and path | Redirect status |
|---|---|---|
| Federation initiate | GET /api/v3/auth/idp/initiate | 302 |
| OIDC federation callback | GET | POST /api/v3/auth/idp/callback | 302 |
| SAML assertion consumer service | POST /api/v3/auth/idp/saml/acs | 303 |
The LDAP credential login leg (POST /api/v3/auth/idp/ldap/login) does not redirect on failure. It fails condition (2): its caller is a JSON-consuming XHR that can read the response body, so the body is never replaced by an interposing CDN. It continues to return 200, 401 and 503 as application/problem+json, byte-identical to before.
This is not a weakening of the RFC 7807 guarantee
Condition (3) is what preserves it. Each of the six codes below is a projection of a typed refusal that a programmatic caller still receives as a problem document on a non-navigation surface. The Error Reference Index states the RFC 7807 guarantee for all API errors; this surface is not one.
Disambiguation from the OAuth 2.0 invalid_request problem type
This reference already documents an error named invalid_request (400). It is a different thing, and the two must not be conflated:
| OAuth 2.0 token-endpoint error | Federated-login redirect code | |
|---|---|---|
| What it is | An RFC 7807 type URI: https://docs.ankatech.co/errors/invalid_request | A value of the error query parameter on a redirect |
| Where it appears | In the body of a 400 response from the OAuth 2.0 token endpoint | In the Location header of a 302 / 303 from a federation leg |
| Which RFC defines the name | RFC 6749 §5.2 (token-endpoint error response) | RFC 6749 §4.1.2.1 (authorization-endpoint error redirect parameter) |
| Media type | application/problem+json | none — there is no body |
| Page | Invalid Request (400) | this page |
Both are named invalid_request deliberately, because both take the name from RFC 6749. The underscore form used by the ANKASecure OAuth 2.0 error pages exists precisely so the RFC 7807 type suffix matches the RFC 6749 error code exactly; the federated-login redirect uses the RFC 6749 name in the redirect-parameter sense the RFC defines for it, which is the closer of the two readings. No page named invalid_request.md is created for this surface — every code is documented here, on one page.
The six codes
The set is closed. A code is never derived from upstream text, so an identity provider cannot inject a value; a value outside this set is treated by the console as unrecognised and rendered generically.
error code | What it means | Who fixes it | Remedy |
|---|---|---|---|
access_not_configured | The identity authenticated at the provider, but the platform will not grant it access — no admissible role matched, the matched role is not assignable, nothing matched at all, or the email address the provider asserted is already owned by another account in the tenant | Operator | Fix the admission rules; if the address is already owned, the operator resolves the duplicate account |
identity_provider_email_unverified | The identity authenticated at the provider, but the provider did not confirm that the email address it asserted belongs to that identity. ANKASecure will not provision an account from an unverified address | The administrator of the identity provider | Verify the address at the identity provider, then sign in again |
identity_provider_not_configured | No enabled identity provider exists for the requested kind or tenant. A configuration state: there is nothing to authenticate against | Operator | Bind and enable a provider |
identity_provider_unavailable | The upstream identity provider could not be reached or did not answer in time | Operator, then the provider | Retry; if it persists, check egress, TLS and timeouts toward the provider |
internal_error | A platform-internal contract defect between ANKASecure services. Not an identity-provider problem and not retryable | ANKASecure engineering | Raise a platform-defect ticket, quoting the ref |
invalid_request | The request that started the login was not usable — for example the "Sign in with SSO" flow was started without a login address, or the federated callback could not be completed with the identity that was asserted | Depends — see below | Retry the login from the console login screen; if it recurs, quote the ref to an operator |
What each code projects
Each code is the browser projection of one or more typed RFC 7807 refusals that are unchanged on every non-navigation surface:
error code | Projected from |
|---|---|
access_not_configured | 403 admission-denied; 422 federated-role-not-assignable; 422 federated-role-not-found; 422 federated-role-s2s-only; 409 federated-email-conflict |
identity_provider_email_unverified | 422 federated-email-not-verified |
identity_provider_not_configured | 503 idp-not-bound |
identity_provider_unavailable | 503 idp-unavailable |
internal_error | 502 federation-internal-error |
invalid_request | 401 federation-callback-invalid; the 400 caller-input refusal on initiate |
The type a programmatic caller sees is the one the authentication service renders
The types in the right-hand column are the ones raised on the internal service-to-service provisioning call. A programmatic caller on a non-navigation surface receives the equivalent refusal from the authentication service: 422 federated-email-not-verified is re-rendered as 422 identity-provider-email-unverified, and every other row in the table above is re-rendered as 403 admission-denied — so the four role and email-conflict causes remain indistinguishable to that caller, exactly as they are in the browser.
invalid_request is a deliberate collapse
Two causes with different owners share this code. On initiate it is the caller's own input (a missing or over-long login address). On the callback it is federation-callback-invalid — the federated login could not be completed with the identity the provider asserted, most commonly because the identity carries no email claim. Collapsing them is intentional: distinguishing them in the browser would tell an unauthenticated caller whether a given login address reached a real provider, which is the enumeration the collapse exists to prevent. The specific cause is recorded server-side against the ref, where an operator can read it and an attacker cannot.
Why the unverified email is told apart and the email collision is not
Two refusals arise from the same place — the identity slot, after the platform has already agreed the identity's roles are grantable — and they are deliberately projected differently.
- The provider asserted an email it did not confirm. That is a fact about the provider's own assertion, and it discloses nothing about ANKASecure: whether an account exists here, whether the address is in use, and whether the tenant has any users are all unchanged by it. So it gets its own code,
identity_provider_email_unverified, because the person who can fix it is the administrator of the identity provider and telling them "access is not configured" sends them to the wrong administrator. - The address is already owned by another account in the tenant. Telling an unauthenticated caller that would confirm an account exists for that address — the account-existence oracle the collapse exists to prevent. It therefore keeps
access_not_configured, byte-identical to a plain admission denial.
The order of the two checks is what keeps the split safe
The unverified-email check runs before the collision check. If it ran second, a caller controlling an identity provider could assert an unverified address and read the returned code as an answer to "does an account already exist for this address?" — access_not_configured for yes, identity_provider_email_unverified for no. Because it runs first, an unverified address always yields the same code regardless of what the platform holds, and the collision branch is only reachable once the address is verified.
Codes never reveal which user, tenant or rule was involved
Every code is oracle-safe. The redirect reveals nothing about whether a user, a tenant, a provider or an admission rule exists. In particular access_not_configured is byte-identical whether the user matched no rule at all, matched a rule naming a role that does not exist, matched a rule naming a role that cannot be assigned in the owning tenant type, was stopped by a domain guard, or presented an email address already owned in the tenant. identity_provider_email_unverified reports only what the provider did not assert, and is likewise byte-identical whatever the platform holds. Diagnosis is performed from the server-side record keyed by ref, never from the response.
Using the ref
ref is a server-authored UUID. It is never taken from a client-supplied value on this surface, and the console validates its shape before rendering it.
For an end user: quote the ref verbatim to whoever administers your ANKASecure deployment.
For an operator: the same value appears in the auth-api log line for the failure, in the admin-api log line for the specific cause, and in the correlationId of the audit event recording the refusal — so one lookup resolves the whole path. The internal troubleshooting runbook describes that lookup.
Verifying on a non-SaaS deployment
initiate's invalid_request arm is reachable only on SaaS deployments. On a private-cloud or on-premise deployment, initiate resolves the deployment-default provider directly and never takes the branch that validates a submitted login address, so the code it can emit is identity_provider_not_configured (or identity_provider_unavailable), not invalid_request.
Measured on the development environment: GET https://dev.ankatech.co/api/v3/auth/idp/initiate returns 503 upstream and therefore redirects with error=identity_provider_not_configured. That is correct behaviour, not a defect — do not file it as one. To exercise the invalid_request arm, use a SaaS deployment.
Related
- Error Reference Index — the RFC 7807 contract for all API errors
- Invalid Request (400) — the OAuth 2.0 token-endpoint error of the same name
- Forbidden (403) — the general authorization refusal
- Service Unavailable (503) — the general availability refusal