Documentation
¶
Overview ¶
Package keycloak enriches OIDC identities from the Keycloak Admin API: composite realm roles, client roles of one client, group paths and user attributes, and refuses users without the required roles or groups. Client.Enrich has the auth.Hook shape, so it plugs into oidc.Options.OnLogin.
The client authenticates with the client credentials grant of a service account by default (the password grant breaks as soon as the admin user gets an OTP credential). It never logs and starts no goroutine.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client reads users' authorization from the Keycloak Admin API. It caches its access token until 30 s before expiry, or until the Admin API answers 401 to it (then the next call requests a new one), and the client UUID of ClientRolesClientID after the first successful lookup. It is safe for concurrent use.
func New ¶
New validates options and returns a Client; it does no I/O. It does not modify options, and changing them afterwards has no effect.
func (*Client) Enrich ¶
Enrich has the auth.Hook shape; identity.Subject is the Keycloak user ID. It returns a Clone with OIDCRoles (composite realm roles), OIDCClientRoles (composite roles of ClientRolesClientID, else none), Groups (paths) and Attributes replaced by the Admin API's answer. A missing required role or group is an *auth.Error{Kind: auth.ErrForbidden} whose Reason names it (never sent to the client); an API failure is a plain error.
Example ¶
package main
import (
"context"
"fmt"
"net/http/httptest"
"github.com/govengers/auth"
"github.com/govengers/auth/internal/fakeprovider"
"github.com/govengers/auth/oidc/keycloak"
)
// startExampleKeycloak serves the test fake in place of Keycloak: realm "staff" with one user and a
// service account client "app-admin".
func startExampleKeycloak() (*httptest.Server, error) {
server := httptest.NewUnstartedServer(nil)
base := "http://" + server.Listener.Addr().String()
handler, err := fakeprovider.NewOIDCHandler(&fakeprovider.OIDCOptions{
IssuerURL: base + "/realms/staff", ClientID: "app", ClientSecret: "app-secret",
KeycloakRealm: "staff", KeycloakClientID: "app-admin", KeycloakClientSecret: "service-secret",
User: fakeprovider.OIDCUser{Subject: "user-1", RealmRoles: []string{"admin"}, Groups: []string{"/staff/scoring"}},
})
if err != nil {
server.Close()
return nil, err
}
server.Config.Handler = handler
server.Start()
return server, nil
}
func main() {
server, err := startExampleKeycloak()
if err != nil {
fmt.Println(err)
return
}
defer server.Close()
client, err := keycloak.New(&keycloak.Options{
URL: server.URL, // e.g. https://sso.example.com
Realm: "staff",
ClientID: "app-admin", // service account with the realm-management roles view-users, query-users and query-groups (docs/wiki/OIDC.md)
ClientSecret: "service-secret",
RequiredRealmRoles: []string{"admin"},
})
if err != nil {
fmt.Println(err)
return
}
// Usually wired as oidc.Options{OnLogin: client.Enrich}; the oidc provider calls it after the callback.
enriched, err := client.Enrich(context.Background(), &auth.Identity{Provider: "keycloak", Subject: "user-1", Active: true})
if err != nil {
fmt.Println(err)
return
}
fmt.Println(enriched.OIDCRoles, enriched.Groups)
}
Output: [admin] [/staff/scoring]
type Options ¶
type Options struct {
URL string // required: Keycloak base URL; https, or http on a loopback host
Realm string // required: realm of the users
AuthenticationRealm string // realm the service account logs into; default Realm (e.g. "master" for a master admin)
GrantType GrantType // default GrantTypeClientCredentials
ClientID string // required for client credentials; password grant default "admin-cli"
ClientSecret string // required for client credentials; optional for the password grant
Username, Password string // required for the password grant
ClientRolesClientID string // optional: clientId whose client roles become OIDCClientRoles
RequiredRealmRoles []string
RequiredClientRoles []string // needs ClientRolesClientID
RequiredGroups []string // group paths ("/parent/child")
HTTPClient *http.Client // default 10 s timeout; a redirect fails the call
}
Options configure a Client.