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 HTTPClientOption
s to configure.
-
clientID string
holds the client ID. -
clientSecret string
holds the client secret. -
tokenURL string
holds the URL of the authentication service that provides an access token. -
credentials clientcredentials.Config
holds the parameters above in anoauth.clientcredentials.Config
instance, used by the underlying OAuth library.
-
NewWithSecret(clientID, clientSecret string, scopeOption ScopeOption, opts ...Option) AuthProvider
returns a new instance ofAuthProvider
. Setting the authentication scope is mandatory. Furthermore, it might receiveOption
s as a parameter. -
TokenSource() oauth2.TokenSource
returns anoauth.clientcredentials.TokenSource
that returns the token until it expires, automatically refreshing it as necessary using the provided context and the client ID and client secret. -
UMATokenSource() UMATokenSource
returns anUMATokenSource
that returns a new token upon everytime a new token is acquired. You might want to use it for authorization purposes. -
HTTPClient(opts ...HTTPClientOption) *http.Client
returns a preconfiguredhttp.Client
. -
ManagedHTTPClient(opts ...HTTPClientOption) *http.Client
returns a preconfiguredhttp.Client
. Uses a thread-safe map to store the created clients, using theclientID
+clientSecret
+tokenURL
combination 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) Option
overrides the base URL of the authentication service. -
WithRealm(realm string) VOption
overrides the realm. -
WithScope(aud string, auds ...string) ScopeOption
sets the authentication scope.
-
WithContext(ctx context.Context) HTTPClientOption
overrides the HTTP context of the client. -
WithBaseClient(bc *http.Client) HTTPClientOption
can 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 ValidatorOption
s to configure.
-
validator JWTValidator
holds theauth0.JWTValidator
instance, used to validate a request. You may find further information about theJWTValidator
interface in the next paragraph. -
baseURL string
holds the base URL of the authentication service. -
realm string
holds the realm. -
keyCacher auth0.KeyCacher
holds the JWK cacher. By default it can hold 5 keys at max, for no longer than 2 hours. -
jwksURL string
holds the keystore URL. -
realmURL string
holds the realm URL. -
signatureAlgorithm jose.SignatureAlgorithm
holds the encryption/decription algorithm of the JWT. By default this isRS256
. -
timeout time.Duration
holds the timeout duration. By default this is 30 seconds.
-
NewValidator(audienceConfig AudienceConfig, opts ...ValidatorOption) ValidatorIntf
returns a new instance ofValidator
. Setting the expected audience is mandatory. Furthermore, it might receiveValidatorOption
s as a parameter. -
ValidateRequest(r *http.Request) error
calls theValidateRequest
function ofauth0.JWTValidator
instance to validate a request. It returnsnil
if the validation has succeeded, otherwise returns anerror
. -
ValidateRequestAndReturnToken(r *http.Request) (*TokenWithClaims, error)
calls theValidateRequest
function ofauth0.JWTValidator
instance to validate a request. It returns the validatedTokenWithClaims
token, that can be used to get the claims if the validation has succeeded, otherwise returns anerror
. -
Middleware(next http.Handler, opts ...HTTPMiddlewareOption) http.Handler
returns anhttp.Handler
instance. It callsValidateRequest
to validate the request. Calls the next middleware if the validation has succeeded, otherwise sends an error using an error writer. It might receiveHTTPMiddlewareOption
s as a parameter. -
EchoMiddlewareFunc(opts ...EchoMiddlewareOption) echo.MiddlewareFunc
returns anecho.MiddlewareFunc
instance. It callsValidateRequest
to validate the request. Calls the nextecho.HandlerFunc
if the validation has succeeded, otherwise returns anerror
. It might receiveEchoMiddlewareOption
s as a parameter. -
HandlerFunc(hf http.HandlerFunc, opts ...HTTPMiddlewareOption) http.HandlerFunc
returns ahttp.HandlerFunc
instance. It callsValidateRequest
to validate the request. Calls the next handler function if the validation has succeeded, otherwise sends an error using an error writer. It might receiveHTTPMiddlewareOption
s 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 []string
holds the audiences.
-
NewAudienceConfig(audience string, audiences ...string) AudienceConfig
returns a newAudienceConfig
. One or more audiences have to be set. -
All() []string
returns 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{}) error
returns the claim for the provided resource's name. -
ValidateScopes(scopes []string) error
check 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 ValidatorOption
s are the following:
-
WithBaseURL(url string) ValidatorOption
overrides the base URL of the authentication service. -
WithSignatureAlgorithm(sa jose.SignatureAlgorithm) ValidatorOption
overrides the encryption/decryption algorithm of the JWT. -
WithRealm(realm string) ValidatorOption
overrides the realm. -
WithKeyCacher(kc auth0.KeyCacher) ValidatorOption
overrides the JWK cacher. -
WithTimeout(timeout time.Duration) ValidatorOption
overrides 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 HTTPMiddlewareOption
s are the following:
WithHTTPErrorWriter(errorWriter func(w http.ResponseWriter, r *http.Request, err error)) HTTPMiddlewareOption
overrides the error writer.
You can configure the echo use-case via passing these Options to Validator
's MiddlewareFunc
function. The available EchoMiddlewareOption
s are the following:
WithContextErrorWriter(errorWriter func(echo.Context, error) error) EchoMiddlewareOption
overrides 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"))