Documentation
¶
Index ¶
- func AbortWithError(ctx *gin.Context, err error)
- func ConfigureGoCloak(f func(c *gocloak.GoCloak)) func(a *KeycloakAuthorizer)
- func NewGinAuthMiddleware(...) (gin.HandlerFunc, error)
- func SetUserContext(ctx *gin.Context, userCtx UserContext)
- type KeycloakAuthorizer
- func (a *KeycloakAuthorizer) ParseAuthorizationHeader(ctx context.Context, authHeader string) (UserContext, error)
- func (a *KeycloakAuthorizer) ParseJWT(ctx context.Context, token string) (UserContext, error)
- func (a *KeycloakAuthorizer) ParseRequest(ctx context.Context, authorizationHeader string, originHeader string) (UserContext, error)
- type KeycloakRealmInfo
- type UserContext
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AbortWithError ¶
func ConfigureGoCloak ¶
func ConfigureGoCloak(f func(c *gocloak.GoCloak)) func(a *KeycloakAuthorizer)
func NewGinAuthMiddleware ¶
func NewGinAuthMiddleware(parseRequestFunc func(ctx context.Context, authorizationHeader string, originHeader string) (UserContext, error)) (gin.HandlerFunc, error)
NewGinAuthMiddleware creates a new Gin middleware to authorize each request via Authorization header in format "BEARER JWT_TOKEN" where JWT_TOKEN is the keycloak auth token. NOTE: Origin header is optional and if not present it will not be tested against the ones in token. It sets the UserContext with extracted token claims in gin context. Use GetUserContext on gin.Context to extract this data.
Example ¶
validToken, clean := setupToken() defer clean() var ( realmId = "user-management" // keycloak realm name authUrl = "http://keycloak:8080/auth" // keycloak server internal url origin = "http://localhost:3000" // request origin, note: it is optional, if request doesn't have Origin header it is not validated ) realmInfo := auth.KeycloakRealmInfo{ RealmId: realmId, AuthServerInternalUrl: authUrl, } authorizer, err := auth.NewKeycloakAuthorizer(realmInfo, authorizerKeycloakMock) // NOTE: authorizerKeycloakMock only used for mocking keycloak cert response in this example, do not use outside tests! if err != nil { log.Fatal(fmt.Errorf("error creating keycloak token authorizer: %w", err)) return } authMiddleware, err := auth.NewGinAuthMiddleware(authorizer.ParseRequest) if err != nil { log.Fatal(fmt.Errorf("error creating keycloak auth middleware: %w", err)) return } gin.SetMode(gin.TestMode) router := gin.New() router.Use(authMiddleware) // wire up middleware router.GET("/test", func(c *gin.Context) { userContext, err := auth.GetUserContext(c) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return } c.String(http.StatusOK, fmt.Sprintf("%#v", userContext)) }) w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/test", nil) req.Header.Add("Authorization", fmt.Sprintf("bearer %s", validToken)) req.Header.Add("Origin", origin) router.ServeHTTP(w, req) fmt.Print(w.Body.String())
Output: auth.UserContext{Realm:"user-management", UserID:"1927ed8a-3f1f-4846-8433-db290ea5ff90", UserName:"initial", EmailAddress:"initial@host.local", Roles:[]string{"offline_access", "uma_authorization", "user", "default-roles-user-management"}, Groups:[]string{"user-management-initial"}, AllowedOrigins:[]string{"http://localhost:3000"}}
func SetUserContext ¶
func SetUserContext(ctx *gin.Context, userCtx UserContext)
SetUserContext adds provided UserContext to gin.Context
Types ¶
type KeycloakAuthorizer ¶
type KeycloakAuthorizer struct {
// contains filtered or unexported fields
}
KeycloakAuthorizer is used to validate if JWT has a correct signature and is valid and returns keycloak claims
func NewKeycloakAuthorizer ¶
func NewKeycloakAuthorizer(realmInfo KeycloakRealmInfo, options ...func(*KeycloakAuthorizer)) (*KeycloakAuthorizer, error)
NewKeycloakAuthorizer creates a new authorizer that checks if issuer is correct keycloak instance and realm and validates JWT signature with public cert from keycloak. It also checks if Origin header mathes allowed origins from the JWT.
Example ¶
validToken, clean := setupToken() defer clean() var ( realmId = "user-management" // keycloak realm name authUrl = "http://keycloak:8080/auth" // keycloak server internal url origin = "http://localhost:3000" // request origin, note: it is optional, if request doesn't have Origin header it is not validated ) realmInfo := auth.KeycloakRealmInfo{ RealmId: realmId, AuthServerInternalUrl: authUrl, } authorizer, err := auth.NewKeycloakAuthorizer(realmInfo, authorizerKeycloakMock) // NOTE: authorizerKeycloakMock only used for mocking keycloak cert response in this example, do not use outside tests! if err != nil { log.Fatal(fmt.Errorf("error creating keycloak token authorizer: %w", err)) return } userContext1, err := authorizer.ParseJWT(context.TODO(), validToken) // pass jwt token here if err != nil { log.Fatal(fmt.Errorf("error parsing token: %w", err)) return } fmt.Printf("%#v\n", userContext1) userContext2, err := authorizer.ParseAuthorizationHeader(context.TODO(), "bearer "+validToken) // pass authorization header here if err != nil { log.Fatal(fmt.Errorf("error parsing token: %w", err)) return } fmt.Printf("%#v\n", userContext2) userContext3, err := authorizer.ParseRequest(context.TODO(), "bearer "+validToken, origin) // pass authorization and origin headers here if err != nil { log.Fatal(fmt.Errorf("error parsing token: %w", err)) return } fmt.Printf("%#v\n", userContext3)
Output: auth.UserContext{Realm:"user-management", UserID:"1927ed8a-3f1f-4846-8433-db290ea5ff90", UserName:"initial", EmailAddress:"initial@host.local", Roles:[]string{"offline_access", "uma_authorization", "user", "default-roles-user-management"}, Groups:[]string{"user-management-initial"}, AllowedOrigins:[]string{"http://localhost:3000"}} auth.UserContext{Realm:"user-management", UserID:"1927ed8a-3f1f-4846-8433-db290ea5ff90", UserName:"initial", EmailAddress:"initial@host.local", Roles:[]string{"offline_access", "uma_authorization", "user", "default-roles-user-management"}, Groups:[]string{"user-management-initial"}, AllowedOrigins:[]string{"http://localhost:3000"}} auth.UserContext{Realm:"user-management", UserID:"1927ed8a-3f1f-4846-8433-db290ea5ff90", UserName:"initial", EmailAddress:"initial@host.local", Roles:[]string{"offline_access", "uma_authorization", "user", "default-roles-user-management"}, Groups:[]string{"user-management-initial"}, AllowedOrigins:[]string{"http://localhost:3000"}}
func (*KeycloakAuthorizer) ParseAuthorizationHeader ¶
func (a *KeycloakAuthorizer) ParseAuthorizationHeader(ctx context.Context, authHeader string) (UserContext, error)
ParseAuthorizationHeader parser an authorization header in format "BEARER JWT_TOKEN" where JWT_TOKEN is the keycloak auth token and returns UserContext with extracted token claims
func (*KeycloakAuthorizer) ParseJWT ¶
func (a *KeycloakAuthorizer) ParseJWT(ctx context.Context, token string) (UserContext, error)
ParseJWT parses and validated JWT token and returns UserContext with extracted token claims
func (*KeycloakAuthorizer) ParseRequest ¶
func (a *KeycloakAuthorizer) ParseRequest(ctx context.Context, authorizationHeader string, originHeader string) (UserContext, error)
ParseRequest parses a request (Authorization header - required; Origin header - optional), validates JWT and returns UserContext with extracted token claims
type KeycloakRealmInfo ¶
type KeycloakRealmInfo struct { RealmId string // RealmId is the realm name that is passed to services via env vars AuthServerInternalUrl string // AuthServerInternalUrl should point to keycloak auth server on internal (not public) network, e.g. http://keycloak:8080/auth; used for contacting keycloak for realm certificate for JWT }
KeycloakRealmInfo provides keycloak realm and server information
type UserContext ¶
type UserContext struct { Realm string UserID string UserName string EmailAddress string Roles []string Groups []string AllowedOrigins []string }
UserContext contains parsed claims from a keycloak JWT token
func GetUserContext ¶
func GetUserContext(ctx *gin.Context) (UserContext, error)
GetUserContext extract UserContext from gin.Context. NOTE: it is immutable, you cannot change the existing context