@@ -2,8 +2,10 @@ package providers
6 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
7 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
8+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
9const keycloakOIDCProviderName = "Keycloak OIDC"
11const keycloakOIDCProviderName = "Keycloak OIDC"
@@ -25,6 +27,15 @@ func NewKeycloakOIDCProvider(p *ProviderData) *KeycloakOIDCProvider {
26var _ Provider = (*KeycloakOIDCProvider)(nil)
28var _ Provider = (*KeycloakOIDCProvider)(nil)
30+// AddAllowedRoles sets Keycloak roles that are authorized.
31+// Assumes `SetAllowedGroups` is already called on groups and appends to that
32+// with `role:` prefixed roles.
33+func (p *KeycloakOIDCProvider) AddAllowedRoles(roles []string) {
34+ for _, role := range roles {
35+ p.AllowedGroups[formatRole(role)] = struct{}{}
28// EnrichSession is called after Redeem to allow providers to enrich session fields
39// EnrichSession is called after Redeem to allow providers to enrich session fields
29// such as User, Email, Groups with provider specific API calls.
40// such as User, Email, Groups with provider specific API calls.
30func (p *KeycloakOIDCProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
41func (p *KeycloakOIDCProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
@@ -36,6 +47,83 @@ func (p *KeycloakOIDCProvider) EnrichSession(ctx context.Context, s *sessions.Se
38func (p *KeycloakOIDCProvider) extractRoles(ctx context.Context, s *sessions.SessionState) error {
49func (p *KeycloakOIDCProvider) extractRoles(ctx context.Context, s *sessions.SessionState) error {
39− // TODO: Implement me with Access Token Role claim extraction logic
50+ claims, err := p.getAccessClaims(ctx, s)
40− return ErrNotImplemented
51+ if err != nil {
56+ roles = append(roles, claims.RealmAccess.Roles...)
57+ roles = append(roles, getClientRoles(claims)...)
59+ // Add to groups list with `role:` prefix to distinguish from groups
60+ for _, role := range roles {
61+ s.Groups = append(s.Groups, formatRole(role))
66+type realmAccess struct {
67+ Roles []string `json:"roles"`
70+type accessClaims struct {
71+ RealmAccess realmAccess `json:"realm_access"`
72+ ResourceAccess map[string]interface{} `json:"resource_access"`
75+func (p *KeycloakOIDCProvider) getAccessClaims(ctx context.Context, s *sessions.SessionState) (*accessClaims, error) {
76+ // HACK: This isn't an ID Token, but has similar structure & signing
77+ token, err := p.Verifier.Verify(ctx, s.AccessToken)
82+ var claims *accessClaims
83+ if err = token.Claims(&claims); err != nil {
89+// getClientRoles extracts client roles from the `resource_access` claim with
90+// the format `client:role`.
92+// ResourceAccess format:
93+// "resource_access": {
107+func getClientRoles(claims *accessClaims) []string {
108+ var clientRoles []string
109+ for clientName, access := range claims.ResourceAccess {
110+ accessMap, ok := access.(map[string]interface{})
112+ logger.Errorf("Unable to parse client roles from claims for client: %v", clientName)
116+ var roles interface{}
117+ if roles, ok = accessMap["roles"]; !ok {
120+ for _, role := range roles.([]interface{}) {
121+ clientRoles = append(clientRoles, fmt.Sprintf("%s:%s", clientName, role))
127+func formatRole(role string) string {
128+ return fmt.Sprintf("role:%s", role)