auth

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 12, 2024 License: AGPL-3.0 Imports: 8 Imported by: 0

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func AbortWithError

func AbortWithError(ctx *gin.Context, err error)

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

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL