Mechanisms

UniAuth supports four mechanisms. The distinction that shapes everything is not what they are called but how the user gets to them.

Mechanism Kind How a user reaches it

Internal store

form-based

the shared username/password form

LDAP

form-based

the shared username/password form

OAuth2 / OIDC

redirect-based

/oauth2/authorization/{registrationId}

SAML 2.0

redirect-based

/saml2/authenticate/{registrationId}

AuthProviderType carries which half a mechanism belongs to, via its formBased flag.

1. Form-based: one form, several providers

The internal store and LDAP share a single form. They are told apart by the AuthenticationProvider chain: every AuthenticationProvider bean is registered onto the chain, and Spring Security tries each until one authenticates.

That fall-through is the point. It is what lets a handful of local break-glass accounts sit beside a directory, so an outage of the directory does not lock every administrator out of the application that depends on it.

2. Redirect-based: their own entry points

OAuth2 and SAML keep their own entry-point URLs, so the chooser page only has to render a link. Nothing in UniAuth proxies the flow — Spring Security’s own filters handle it, and the starter’s job is to have installed them onto the same chain.

3. Asking what is available

AuthProviderRegistry is the single answer to "what can a user sign in with right now". The chooser page reads it, and so does the JSON endpoint:

curl http://localhost:8080/uniauth/providers
[
  {"id":"internal","type":"INTERNAL","brand":"GENERIC","displayName":"Local account",
   "loginUrl":null,"oidc":false,"formBased":true},
  {"id":"google","type":"OAUTH2","brand":"GOOGLE","displayName":"Google",
   "loginUrl":"/oauth2/authorization/google","oidc":true,"formBased":false},
  {"id":"github","type":"OAUTH2","brand":"GITHUB","displayName":"GitHub",
   "loginUrl":"/oauth2/authorization/github","oidc":false,"formBased":false}
]

An API-first front end can render its own chooser from this without knowing anything about Spring Security.

The registry does not read those repositories itself — each redirect-based mechanism contributes its own entries, which is what lets both jars be optional. A mechanism enumerates its repository by testing it for Iterable, which the in-memory implementations Boot produces are. A custom repository — database-backed, say — is not iterable, so its mechanism still works in the chain but cannot be listed.

4. HTTP Basic, for callers that are not browsers

All three login mechanisms assume a browser and a session, which leaves no way in for a smoke check, a polling worker or a line of curl: none can perform an OAuth redirect, and a form login means scraping a CSRF token and holding a cookie jar to fetch a health page.

uniauth:
  http-basic:
    enabled: true          # off by default
    paths: [ /api/** ]     # optional: where the challenge is offered

Basic answers the form-based mechanisms — the internal store and LDAP. The redirect-based ones have no password to present, so the question does not arise for them.

A browser never gets the challenge — not when it navigates, and not when a page it is already showing calls in the background. Installed naively, Basic answers an unauthenticated browser with a native credential dialog instead of the login page, which defeats the point of offering OAuth at all; and that dialog opens above the page, so a front end cannot suppress it.

Browsers say so themselves. Every current one labels its requests with Sec-Fetch-* headers, which curl and the usual HTTP libraries do not send. So a navigation falls through to the chooser, a fetch() or XHR gets a bare 401 with no WWW-Authenticate — the answer a front end can act on, by sending the user to the chooser itself — and only a caller that did not identify itself is challenged. Programs that present credentials up front, as curl -u and most HTTP libraries do, never waited for a challenge anyway.

paths scopes the challenge, not whether credentials are accepted. Scoping acceptance would mean narrowing this chain, and it is the catch-all: it would stop UniAuth protecting everything outside those paths, which is far more than the property appears to ask for.

5. Brand is not a mechanism, and neither is OIDC

Three separate axes, deliberately not merged:

AuthProviderType

The mechanism — how the provider talks, and therefore which filter installs it. Adding GOOGLE here as a peer of OAUTH2 would break the form/redirect split the chain depends on, and the list would grow without bound.

AuthProviderBrand

Presentation only. Icons and vendor-mandated button treatment.

AuthProvider.oidc()

Capability, derived from the openid scope. This is the one that changes what your code receives: Google yields an OidcUser with claims, GitHub yields a bare OAuth2User. Reach for this whenever you mean "does it have claims", never for brand.

That last distinction is why the examples ship Google and GitHub side by side: it is visible rather than asserted.