OAuth2-Proxy Group Authorization#
Group authorization in oauth2-proxy controls which authenticated users gain access based on group membership or role assignment. The core logic lives in Authorize() in providers/provider_default.go, and applies to all providers uniformly.
Authorization Logic: OR-Based Membership#
Authorize() does a simple set lookup :
- If
AllowedGroupsis empty, every authenticated user passes β authorization is effectively disabled. - If
AllowedGroupsis non-empty, the user'ssession.Groupsslice is iterated; the first match against any allowed group grants access (OR semantics). A user needs membership in only one of the listed groups/roles. - Matching is exact string equality β no wildcards, no prefix matching, no regex. The group string stored in the session must exactly match the string in
AllowedGroups.
AllowedGroups is a map[string]struct{} built at startup from the --allowed-group flag (and, for Keycloak OIDC, --allowed-role) via setAllowedGroups. Empty strings are silently dropped during initialization.
Keycloak OIDC: Two Sources Feeding session.Groups#
For --provider=keycloak-oidc (providers/keycloak_oidc.go), session.Groups is populated from two distinct sources:
| Source | Token | Claim | Stored as |
|---|---|---|---|
| Realm roles | Access token (JWT) | realm_access.roles | role:<rolename> |
| Client roles | Access token (JWT) | resource_access.<client>.roles | role:<client>:<rolename> |
| OIDC groups | ID token | groups (or --oidc-groups-claim) | raw string, e.g. /groupname |
Role extraction is performed by extractRoles(), called from EnrichSession, RefreshSession, and CreateSessionFromToken so roles stay in sync with the session at all lifecycle points. Roles get the role: prefix via formatRole() before being appended to session.Groups; this prefix is what distinguishes roles from OIDC groups and makes them route through the same Authorize() check.
Allowed roles declared via --allowed-role are registered (with the same role: prefix) into AllowedGroups at startup by addAllowedRoles().
OIDC groups flow through the standard OIDC path in buildSessionFromClaims, reading the claim named by --oidc-groups-claim from the ID token. They are stored verbatim β no prefix is added.
Group Path Prefixes and Exact Matching (Keycloak)#
Keycloak's "Full group path" mapper option controls whether groups are emitted as /groupname (full path) or groupname (short name) .
The string used in --allowed-group must exactly match what Keycloak puts in the token :
- If "Full group path" is enabled in the Keycloak Group Membership mapper β use
--allowed-group=/groupname(leading/required) or--allowed-group=/groupname/child_groupfor nested groups. - If "Full group path" is disabled β use
--allowed-group=groupname(no/).
A mismatch here (e.g., using groupname when the token contains /groupname) will silently fail β the user is denied because no string equality match is found.
Configuration Reference#
| Flag | Provider | What it authorizes |
|---|---|---|
--allowed-group=/groupname | Any OIDC | Users with that OIDC group in their ID token |
--allowed-role=myrole | keycloak-oidc only | Users with Keycloak realm role myrole |
--allowed-role=myclient:myrole | keycloak-oidc only | Users with client role myrole on client myclient |
For Keycloak group authorization to work, a "groups" client scope with a Group Membership mapper must be created in Keycloak and attached to the OIDC client . Role authorization requires no extra Keycloak setup since the standard "roles" client scope ships with realm/client role mappers enabled by default .
Key Source Files#
| File | Purpose |
|---|---|
providers/provider_default.go | Authorize() β the OR-based membership check |
providers/keycloak_oidc.go | Role extraction, addAllowedRoles, formatRole |
providers/provider_data.go | setAllowedGroups, AllowedGroups field |
docs/configuration/providers/keycloak_oidc.md | Full Keycloak setup walkthrough |
| Official docs | Public reference |