10 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
11 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
14const keycloakOIDCProviderName = "Keycloak OIDC"
16// KeycloakOIDCProvider creates a Keycloak provider based on OIDCProvider
17type KeycloakOIDCProvider struct {
21// NewKeycloakOIDCProvider makes a KeycloakOIDCProvider using the ProviderData
22func NewKeycloakOIDCProvider(p *ProviderData, opts options.Provider) *KeycloakOIDCProvider {
23 p.setProviderDefaults(providerDefaults{
24 name: keycloakOIDCProviderName,
27 provider := &KeycloakOIDCProvider{
28 OIDCProvider: NewOIDCProvider(p, opts.OIDCConfig),
31 provider.addAllowedRoles(opts.KeycloakConfig.Roles)
35var _ Provider = (*KeycloakOIDCProvider)(nil)
37// addAllowedRoles sets Keycloak roles that are authorized.
38// Assumes `SetAllowedGroups` is already called on groups and appends to that
39// with `role:` prefixed roles.
40func (p *KeycloakOIDCProvider) addAllowedRoles(roles []string) {
41 if p.AllowedGroups == nil {
42 p.AllowedGroups = make(map[string]struct{})
44 for _, role := range roles {
45 p.AllowedGroups[formatRole(role)] = struct{}{}
49// CreateSessionFromToken converts Bearer IDTokens into sessions
50func (p *KeycloakOIDCProvider) CreateSessionFromToken(ctx context.Context, token string) (*sessions.SessionState, error) {
51 ss, err := p.OIDCProvider.CreateSessionFromToken(ctx, token)
53 return nil, fmt.Errorf("could not create session from token: %v", err)
56 // Extract custom keycloak roles and enrich session
57 if err := p.extractRoles(ss); err != nil {
64// EnrichSession is called after Redeem to allow providers to enrich session fields
65// such as User, Email, Groups with provider specific API calls.
66func (p *KeycloakOIDCProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
67 err := p.OIDCProvider.EnrichSession(ctx, s)
69 return fmt.Errorf("could not enrich oidc session: %v", err)
71 return p.extractRoles(s)
74// RefreshSession adds role extraction logic to the refresh flow
75func (p *KeycloakOIDCProvider) RefreshSession(ctx context.Context, s *sessions.SessionState) (bool, error) {
76 refreshed, err := p.OIDCProvider.RefreshSession(ctx, s)
78 // Refresh could have failed or there was not session to refresh (with no error raised)
79 if err != nil || !refreshed {
83 return true, p.extractRoles(s)
86func (p *KeycloakOIDCProvider) extractRoles(s *sessions.SessionState) error {
87 claims, err := p.getAccessClaims(s)
94 roles = append(roles, claims.RealmAccess.Roles...)
95 roles = append(roles, getClientRoles(claims)...)
97 // Add to groups list with `role:` prefix to distinguish from groups
98 for _, role := range roles {
99 s.Groups = append(s.Groups, formatRole(role))
104type realmAccess struct {
105 Roles []string `json:"roles"`
108type accessClaims struct {
109 RealmAccess realmAccess `json:"realm_access"`
110 ResourceAccess map[string]interface{} `json:"resource_access"`
113func (p *KeycloakOIDCProvider) getAccessClaims(s *sessions.SessionState) (*accessClaims, error) {
114 parts := strings.Split(s.AccessToken, ".")
116 return nil, fmt.Errorf("malformed access token, expected 3 parts got %d", len(parts))
119 payload, err := base64.RawURLEncoding.DecodeString(parts[1])
121 return nil, fmt.Errorf("malformed access token, couldn't extract jwt payload: %v", err)
124 var claims accessClaims
125 if err := json.Unmarshal(payload, &claims); err != nil {
131// getClientRoles extracts client roles from the `resource_access` claim with
132// the format `client:role`.
134// ResourceAccess format:
136// "resource_access": {
150func getClientRoles(claims *accessClaims) []string {
151 var clientRoles []string
152 for clientName, access := range claims.ResourceAccess {
153 accessMap, ok := access.(map[string]interface{})
158 var roles interface{}
159 if roles, ok = accessMap["roles"]; !ok {
162 for _, role := range roles.([]interface{}) {
163 clientRoles = append(clientRoles, fmt.Sprintf("%s:%s", clientName, role))
169func formatRole(role string) string {
170 return fmt.Sprintf("role:%s", role)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list