Authorization and Authentication for backend services | 221205
221205
Authorization and Authentication for backend services
I have got the Authorization to state of reasonable operation and broken down into proper middlewares for Token Verification and Token Introspective. I did introduce a sub handler that I will need to propagate to all existing services and future.
Sub Handler ( Generic Handler )
type ServiceHandler struct {
ServiceName string
ServiceLogger *log.Logger
validator *internal.Validation
}
func NewHandler(serviceName string, serviceLogger *log.Logger, validator *internal.Validation) *ServiceHandler {
return &ServiceHandler{serviceName, serviceLogger, validator}
}
This is similar to my existing handler, but each service has there own, which will still exist but those individual handler will now wrap this new Handler.
type DevTest struct {
GenericHandler *handlers.ServiceHandler
}
func NewDevTestHandler(h *handlers.ServiceHandler) *DevTest {
return &DevTest{h}
}
example use.
const serviceName = "test-dev-service"
APILogger := log.New(os.Stdout, "test-dev-service | ", log.LstdFlags)
validation := internal.NewValidation()
gHandler := auth.NewHandler(serviceName, APILogger, validation)
devTestHandler := handlers.NewDevTestHandler(gHandler)
router := mux.NewRouter()
router.Use(devTestHandler.GenericHandler.AuthenticationMiddlewareViaTokenIntrospective)
postRouter := router.Methods(http.MethodPost).Subrouter()
//postRouter.Use(devTestHandler.GenericHandler.AuthenticationMiddlewareViaLocalVerification)
postRouter.HandleFunc("/", devTestHandler.PostTest)
func (ha *ServiceHandler) AuthenticationMiddlewareViaTokenIntrospective(next http.Handler) http.Handler {
...
This new generic handler is where the new Authentication middleware is accessed. It was done this way that each service can still have it own separate handler while still have in access to it parent function ie the new Auth functions.
With both individuals service and generic handler both being handlers you can chain them together using the standard way. This allows for you insert the Authentication middleware anywhere in the chain or call all of then(there are only two currently)
Here is an example. You want to check all every request to a service with the Local token verification, then on certain REST operations you want to do the token introspection.
This is a common way to do things just use the local jwt verification the majority of the time, but on deletes or any other sensitive operations do the token introspective just make sure.
gHandler := auth.NewHandler(serviceName, APILogger, validation)
devTestHandler := handlers.NewDevTestHandler(gHandler)
router := mux.NewRouter()
router.Use(devTestHandler.GenericHandler.AuthenticationMiddlewareViaLocalVerification)
postRouter := router.Methods(http.MethodPost).Subrouter()
postRouter.Use(devTestHandler.GenericHandler. AuthenticationMiddlewareViaTokenIntrospective)
postRouter.HandleFunc("/", devTestHandler.PostTest)
Setting Auth to Context
I created a struct that inserts the Authentication information into the context of the request
I am not happiest on the naming of these fields, so these mite change
type authentication struct {
//newAuthentication.ExternalID the 'sid' in the JWT this will be used to id the user in this Application and the external SSO
ExternalID uuid.UUID
//newAuthentication.UserPrincipalName the 'upn' in the JWT
UserPrincipalName string
//newAuthentication.JWTIssuer the 'iss' in the JWT
JWTIssuer string
//newAuthentication.JWTAuthorizationParty the 'azp' in the JWT
JWTAuthorizationParty string
//newAuthentication.JWTIssuerGroups the 'groups' in teh JWT
JWTIssuerGroups []string
//newAuthentication.JWTAuthorizationTime the 'auth_time' in the JWT
JWTAuthorizationTime *time.Time
//newAuthentication.JWTExpiration the 'exp' in the JWT
JWTExpiration *time.Time
//newAuthentication.LastTokenLocalVerification The time of the last local Verification of the token
LastVerificationTime *time.Time
LastVerificationOperationType authenticationVerificationType
}
type authenticationVerificationType interface {
GetAuthenticationVerificationType() string
}
type localAuthenticationVerificationType struct {
authenticationVerificationType
}
func (t *localAuthenticationVerificationType) GetAuthenticationVerificationType() string {
return "Local-Token-Verification"
}
type tokenIntrospectionAuthenticationVerificationType struct {
authenticationVerificationType
}
func (t *tokenIntrospectionAuthenticationVerificationType) GetAuthenticationVerificationType() string {
return "Token-Introspective-Verification"
}
Upon a successful Introspective or Verification the information from the token is transfer into the struct above with the addition of the time of check and type of check.
If you have worked with JWTs before you will know that is not all the fields of a standard token, but I really could think of use case for all the fields in a standard token and claim. I selected just few I have plans to use or think they could be potentially useful.
I was thinking if couldn’t think use case for the data then there is no need to replicate it again into this struct cause technically this information just copied from the token itself, but just in a fetchable from.
Currently this struct is being set into context of the dealing with that request.
func SetAuthenticationInContext(ctx context.Context, a *authentication) context.Context {
ctx = context.WithValue(ctx, KeyAuthentication{}, a)
return ctx
}
I do for see this possible being an issue with saga pattern implementation when they do a transaction with REST call as there action due it no passing the context. I am some ideas to deal with that I will either have to make all services use POST and attach this struct to the the request, PUT the struct in the header, or just pass token in the header to the new request and let the existing system deal with it.
That last option makes the most sense, but I would like some way to keep track when a request be sent via another service on behalf of a user. I am going to have think on this some more.
Context Authentication Retrieval
Some may have noticed that or if you have look on the github for this. That the Authentication struct is private and thus not accessible outside of it package. This was done to give the services the availability to easily edit the current authentication. To get access to each of the fields I created getters to grab the desired fields from the context it self.
func (DevObj *DevTest) PostTest(rw http.ResponseWriter, r *http.Request) {
// fetch the servers from the uuid
rw.Header().Add("Content-Type", "application/json")
uuid, err := handlers.AuthGetExternalIDFromContext(r.Context())
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
...
so if one of the services wants use the sid or any other field they just need to call it and check for errors( I put in a error to catch for null, cause getting a value from context can just return a null)
I am going to implement these changes to the existing services, then I’ll be on to Users.
Forward…