This package is a very thin layer over Go's standard OAuth2 library and the Auth0 library for Go, extending their functionality via introducing an additional layer that handles the initialization and communication with our current authorization provider Keycloak.
This package provides both client-side and server-side wrappers, covering all of our current use-cases. In this document, you may find useful information about the APIs, the custom configuration options, and the usage as well.
The client-side validation logic is located in the client package. The package offers a convenient way to gain an access token on the client-side. It was achieved by extending Go's standard http.Client. It basically holds the necessary parameters for a successful token request (like client ID, client secret, and the token URL of the authorization server). You can use the AuthProvider in several different ways to gain an access token. You may find information about each use-case in the API paragraph. One important thing to note is that in this context client means service client, participating in service-to-service authentication.
Describes the possible operations and use-cases of our package.
Implements the AuthProvider interface. This class is used to gain an authenticated http.Client to make further authenticated HTTP calls, or alternatively, a token source can be created as well, but in this case, only the access token can be gained, not a complete authenticated http.Client. You can use HTTPClientOptions to configure.
-
clientID stringholds the client ID. -
clientSecret stringholds the client secret. -
tokenURL stringholds the URL of the authentication service that provides an access token. -
credentials clientcredentials.Configholds the parameters above in anoauth.clientcredentials.Configinstance, used by the underlying OAuth library.
-
NewWithSecret(clientID, clientSecret string, scopeOption ScopeOption, opts ...Option) AuthProviderreturns a new instance ofAuthProvider. Setting the authentication scope is mandatory. Furthermore, it might receiveOptions as a parameter. -
TokenSource() oauth2.TokenSourcereturns anoauth.clientcredentials.TokenSourcethat returns the token until it expires, automatically refreshing it as necessary using the provided context and the client ID and client secret. -
UMATokenSource() UMATokenSourcereturns anUMATokenSourcethat returns a new token upon everytime a new token is acquired. You might want to use it for authorization purposes. -
HTTPClient(opts ...HTTPClientOption) *http.Clientreturns a preconfiguredhttp.Client. -
ManagedHTTPClient(opts ...HTTPClientOption) *http.Clientreturns a preconfiguredhttp.Client. Uses a thread-safe map to store the created clients, using theclientID+clientSecret+tokenURLcombination as a key. When the function is called, it will try to retrieve an existing instance from the map by the credentials. If found, the instance will be returned. Otherwise, a new instance will be created, saved in the map, and returned.
This is responsible for providing authorization purposes. Similarly to the regular ouath2.TokenSource, it has a Token() method that returns a new token upon each invocation.
Token(payload interface{}, permisson []Permission, audienceConfig config.AudienceConfig) (*oauth2.Token, error)returns a new token upon each invocation.
It represents an authorization permission.
The package offers wide configurability using Options. You can easily override any parameter by passing the desired Option(s) as constructor arguments. Not only the AuthProvider itself has Options, but each use-case has their own Options as well, offering further configuration possibilities.
-
WithBaseURL(baseURL string) Optionoverrides the base URL of the authentication service. -
WithRealm(realm string) VOptionoverrides the realm. -
WithScope(aud string, auds ...string) ScopeOptionsets the authentication scope.
-
WithContext(ctx context.Context) HTTPClientOptionoverrides the HTTP context of the client. -
WithBaseClient(bc *http.Client) HTTPClientOptioncan extend an already existing HTTP client.
authProvider := client.NewWithSecret("my-client-id", "my-client-secret")
resp, err := authProvider.ManagedHTTPClient().Get("https://myservice.services.bitrise.io/")The server-side validation logic is located in the service package. You can use the Validator in several different ways to validate any request. The supported use-cases are the following:
- Handler Function with:
- Go's default HTTP multiplexer
- Gorilla's HTTP router called gorilla/mux
- Middleware with:
- Go's default HTTP multiplexer
- Gorilla's HTTP router called gorilla/mux
- Middleware Function and Handler Function with Labstack's router called echo
Describes the possible operations and use-cases of our package.
Implements the ValidatorIntf interface. As its name reflects, this class is responsible for the validation of a request, using an auth0.Validator instance.
You can use ValidatorOptions to configure.
-
validator JWTValidatorholds theauth0.JWTValidatorinstance, used to validate a request. You may find further information about theJWTValidatorinterface in the next paragraph. -
baseURL stringholds the base URL of the authentication service. -
realm stringholds the realm. -
keyCacher auth0.KeyCacherholds the JWK cacher. By default it can hold 5 keys at max, for no longer than 2 hours. -
jwksURL stringholds the keystore URL. -
realmURL stringholds the realm URL. -
signatureAlgorithm jose.SignatureAlgorithmholds the encryption/decription algorithm of the JWT. By default this isRS256. -
timeout time.Durationholds the timeout duration. By default this is 30 seconds.
-
NewValidator(audienceConfig AudienceConfig, opts ...ValidatorOption) ValidatorIntfreturns a new instance ofValidator. Setting the expected audience is mandatory. Furthermore, it might receiveValidatorOptions as a parameter. -
ValidateRequest(r *http.Request) errorcalls theValidateRequestfunction ofauth0.JWTValidatorinstance to validate a request. It returnsnilif the validation has succeeded, otherwise returns anerror. -
ValidateRequestAndReturnToken(r *http.Request) (*TokenWithClaims, error)calls theValidateRequestfunction ofauth0.JWTValidatorinstance to validate a request. It returns the validatedTokenWithClaimstoken, that can be used to get the claims if the validation has succeeded, otherwise returns anerror. -
Middleware(next http.Handler, opts ...HTTPMiddlewareOption) http.Handlerreturns anhttp.Handlerinstance. It callsValidateRequestto validate the request. Calls the next middleware if the validation has succeeded, otherwise sends an error using an error writer. It might receiveHTTPMiddlewareOptions as a parameter. -
EchoMiddlewareFunc(opts ...EchoMiddlewareOption) echo.MiddlewareFuncreturns anecho.MiddlewareFuncinstance. It callsValidateRequestto validate the request. Calls the nextecho.HandlerFuncif the validation has succeeded, otherwise returns anerror. It might receiveEchoMiddlewareOptions as a parameter. -
HandlerFunc(hf http.HandlerFunc, opts ...HTTPMiddlewareOption) http.HandlerFuncreturns ahttp.HandlerFuncinstance. It callsValidateRequestto validate the request. Calls the next handler function if the validation has succeeded, otherwise sends an error using an error writer. It might receiveHTTPMiddlewareOptions as a parameter.
Since auth0.JWTValidator is not an interface, it was necessary to create an interface to loosen the coupling and making it exchangeable and mockable in tests.
You can set the expceted audience via passing an AudienceConfig instance as a parameter upon instantiating the validator. One or more audience have to passed.
audience []stringholds the audiences.
-
NewAudienceConfig(audience string, audiences ...string) AudienceConfigreturns a newAudienceConfig. One or more audiences have to be set. -
All() []stringreturns all of the audiences.
validator := service.NewValidator(config.NewAudienceConfig("audience1", "audience2"),
service.WithBaseURL("https://auth.services.bitrise.io"), service.WithRealm("master"))Represents an UMA token that holds certain claims.
-
Payload() (map[string]interface{}, error)returns the contents of the token (basically all the claims in the token) -
Permissions() ([]interface{}, error)returns the persmissions part of the token. -
Claim(resourceName string, claim interface{}) errorreturns the claim for the provided resource's name. -
ValidateScopes(scopes []string) errorcheck if the token has ALL the passed scopes in its scope claim
err := tokenWithClaims.ValidateScopes([]string{"app:read", "missing:write"})
if err != nil {
// scope validation failed
}The package offers wide configurability using Options. You can easily override any parameter by passing the desired Option(s) as constructor arguments. Not only the Validator itself has Options, but each use-case has their own Options as well, offering further configuration possibilities.
If you want to override the default options (like the auth service url, or the realm), you can customize the Validator during instantiation with Options. It's as easy as passing them as constructor parameters, separated by a comma:
service.NewValidator(config.NewAudienceConfig("audience"),
service.WithBaseURL("https://auth.services.bitrise.io"), service.WithRealm("master"))The available ValidatorOptions are the following:
-
WithBaseURL(url string) ValidatorOptionoverrides the base URL of the authentication service. -
WithSignatureAlgorithm(sa jose.SignatureAlgorithm) ValidatorOptionoverrides the encryption/decryption algorithm of the JWT. -
WithRealm(realm string) ValidatorOptionoverrides the realm. -
WithKeyCacher(kc auth0.KeyCacher) ValidatorOptionoverrides the JWK cacher. -
WithTimeout(timeout time.Duration) ValidatorOptionoverrides the timeout for validation networking.
You can configure the Handler Function and Middleware use-cases via passing these Options either to Validator's HandlerFunc or Middleware function. The available HTTPMiddlewareOptions are the following:
WithHTTPErrorWriter(errorWriter func(w http.ResponseWriter, r *http.Request, err error)) HTTPMiddlewareOptionoverrides the error writer.
You can configure the echo use-case via passing these Options to Validator's MiddlewareFunc function. The available EchoMiddlewareOptions are the following:
WithContextErrorWriter(errorWriter func(echo.Context, error) error) EchoMiddlewareOptionoverrides the error writer.
func someHandler(w http.ResponseWriter, r *http.Request) {
token, err := validator.ValidateRequestAndReturnToken(r)
if err != nil {
panic(err)
}
claims, err := token.Payload()
if err != nil {
panic(err)
}
claimsResponse := fmt.Sprintf("%v", claims)
w.Write([]byte(claimsResponse))
}handler := func(w http.ResponseWriter, r *http.Request) {}
mux := http.NewServeMux()
validator := service.NewValidator(config.NewAudienceConfig("audience"),
service.WithBaseURL("https://auth.services.bitrise.io"), service.WithRealm("master"))
mux.HandleFunc("/test_func", validator.HandlerFunc(handler))
log.Fatal(http.ListenAndServe(":8080", mux))handler := func(w http.ResponseWriter, r *http.Request) {}
router := mux.NewRouter()
validator := service.NewValidator(config.NewAudienceConfig("audience"),
service.WithBaseURL("https://auth.services.bitrise.io"), service.WithRealm("master"))
router.HandleFunc("/test_func", validator.HandlerFunc(handler)).Methods(http.MethodGet)
http.Handle("/", router)
log.Fatal(http.ListenAndServe(":8080", router))handler := func(w http.ResponseWriter, r *http.Request) {}
mux := http.NewServeMux()
validator := service.NewValidator(config.NewAudienceConfig("audience"),
service.WithBaseURL("https://auth.services.bitrise.io"), service.WithRealm("master"))
mux.Handle("/test", validator.Middleware(http.HandlerFunc(handler)))
log.Fatal(http.ListenAndServe(":8080", mux))handler := func(w http.ResponseWriter, r *http.Request) {}
router := mux.NewRouter()
validator := service.NewValidator(config.NewAudienceConfig("audience"),
service.WithBaseURL("https://auth.services.bitrise.io"), service.WithRealm("master"))
router.Handle("/test", validator.Middleware(http.HandlerFunc(handler))).Methods(http.MethodGet)
http.Handle("/", router)
log.Fatal(http.ListenAndServe(":8080", router))handler := func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
}
e := echo.New()
validator := service.NewValidator(config.NewAudienceConfig("audience"),
service.WithBaseURL("https://auth.services.bitrise.io"), service.WithRealm("master"))
e.Use(validator.MiddlewareFunc())
e.GET("/test", handler)
e.Logger.Fatal(e.Start(":8080"))validator := service.NewValidator(config.NewAudienceConfig("audience"),
service.WithBaseURL("https://auth.services.bitrise.io"), service.WithRealm("master"))
handler := func(c echo.Context) error {
if err := validator.ValidateRequest(c.Request()); err != nil {
return err
}
return c.String(http.StatusOK, "Hello, World!")
}
e := echo.New()
e.GET("/test", handler)
e.Logger.Fatal(e.Start(":8080"))