16 - Create Gin Middleware to Extract Authorized User
Tags: go, gin, authentication, middleware
Published: 2021-01-04 01:22:56

---
title: 16 - Create Gin Middleware to Extract Authorized User
published: true
description: Setting up an account server in Golang. Today we create a middleware for extracting a user's information from a JSON Web Token used as an id, or access, token.
tags: golang, gin, authentication, middleware
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/hq8olek6iye3z3sqo7ng.png
series: Full Stack Memorization App
---

Today we'll create a middleware to extract a user from an ID token sent to our application on an `Authorization` header of an HTTP request. This ID token is the one we send to a user in JWT format when they sign up or sign in. 

The middleware will do the following:

1. verify that we've received the appropriate `Authorization` header. 
2. check that the token is valid and not expired. 
3. "set" the user on the Gin context so that route handlers can use the verified user data to make authorized updates for this user (like updating their profile, for example). 

In some role- or permission-based authorization schemes, the token might store a role, or the role might be fetched from a database based on an ID stored in the token. In our application, each user will only be authorized to make changes to their own account info, so we will not be looking up or assigning any permissions.

![Auth User Middleware](https://dev-to-uploads.s3.amazonaws.com/i/rh5kekajdmwvmbsetxh5.png)

In order to create this middleware, we'll need to add a new method to our our `TokenService` called `ValidateIDToken`. As `middleware` is a sub-package of `handler`, we'll have to pass the `TokenService` as a parameter to the middleware.

After we've completed these tasks, we'll be able to run our application and send a Get Request with a user's valid ID token to the "/me" endpoint and receive the user's details.

*As always, check out the [Github repo](https://github.com/JacobSNGoodwin/memrizr) with all of the code for this tutorial including a branch for each lesson and some unit tests not included in the tutorials!*

*If you prefer video* 
{% youtube CfLwB5UBeOQ %}

## Review of Me Handler

If we look at the current code of our "me" handler, we see that we extract a user from the gin context with a `Get` method. This user is then cast to a `*model.User`. The middleware we're going to write will make sure that a user is available on the "user" key when this handler method is executed.

```go
func (h *Handler) Me(c *gin.Context) {
	// A *model.User will eventually be added to context in middleware
	user, exists := c.Get("user")

  // This shouldn't happen, as our middleware ought to throw an error.
	// We can also use "MustGet" to get the key or panic
	if !exists {
		log.Printf("Unable to extract user from request context for unknown reason: %v\n", c)
		err := apperrors.NewInternal()
		c.JSON(err.Status(), gin.H{
			"error": err,
		})

		return
	}

  uid := user.(*model.User).UID
  
  // code omitted
}
```

## Fix UserService.Signin

I previously failed to update the `*model.User` passed as a method parameter to `UserService.Signin` with the user returned by `UserRepository.FindByEmail`.

Let's fix this as follows:

```go
  // prev
  u = uFetched

  // May be better to return new user from method
  *u = *uFetched
```

If I had to do things again, I would pass an `email` and a `password` (or else a struct containing these fields) to `UserService.Signin`, and return `(*model.User, error)`. I believe this approach would be much easier to understand. 

To avoid having to modify our tests and `handler.Signin`, we won't fix this now, but feel free to do this in your app.

## Add a validateIDToken Function

We previously created a file with some utility functions for working with tokens in `~/service/tokens.go`. Let's now update this file with with a function to validate an ID token.

```go
// IDTokenCustomClaims holds structure of jwt claims of idToken
type IDTokenCustomClaims struct {
	User *model.User `json:"user"`
	jwt.StandardClaims
}

// CODE OMITTED ...

// validateIDToken returns the token's claims if the token is valid
func validateIDToken(tokenString string, key *rsa.PublicKey) (*IDTokenCustomClaims, error) {
	claims := &IDTokenCustomClaims{}

	token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
		return key, nil
	})

	// For now we'll just return the error and handle logging in service level
	if err != nil {
		return nil, err
	}

	if !token.Valid {
		return nil, fmt.Errorf("ID token is invalid")
	}

	claims, ok := token.Claims.(*IDTokenCustomClaims)

	if !ok {
		return nil, fmt.Errorf("ID token valid but couldn't parse claims")
	}

	return claims, nil
}
```

This function takes in the stringified version of our JWT, `tokenString`, and the `rsa.PublicKey` which is a field of our `tokenService`. We first parse the JWT using the public RSA key. This will return a `*jwt.Token`. This token will contain a `valid` field indicating whether the token is valid (including if it is expired). It also contains the claims, or fields, of the token. We cast these claims to `*IDTokenCustomClaims` and then return them.

## Use validateIDToken in TokenService.ValidateIDToken

### Add Method to Interfaces

We need to update the `TokenService` interface by creating the method signature for `ValidateIDToken`. We do this in `~/model/interfaces.go`.

```go
// TokenService defines methods the handler layer expects to interact
// with in regards to producing JWTs as string
type TokenService interface {
	NewPairFromUser(ctx context.Context, u *User, prevTokenID string) (*TokenPair, error)
	ValidateIDToken(tokenString string) (*User, error)
}
```

We need not pass a context as this method will not be making calls to the repository layer and because our token functions do not require a context.

### Create Mock ValidateIDToken

To get rid of warnings, I'll add a mock definition in `~/model/mocks/token_service.go`.

```go
// ValidateIDToken mocks concrete ValidateIDToken
func (m *MockTokenService) ValidateIDToken(tokenString string) (*model.User, error) {
	ret := m.Called(tokenString)

	// first value passed to "Return"
	var r0 *model.User
	if ret.Get(0) != nil {
		// we can just return this if we know we won't be passing function to "Return"
		r0 = ret.Get(0).(*model.User)
	}

	var r1 error

	if ret.Get(1) != nil {
		r1 = ret.Get(1).(error)
	}

	return r0, r1
}
```

### ValidateIDToken Implementation

The implementation of `TokenService.ValidateIDToken` will be pretty simple. The main purpose is to extract and return the `*model.User` from off of the `IDTokenCustomClaims`. Let's update `~/service/token_service.go`.

In the case of any error from `validateIDToken`, we'll return an authorization error.

```go
// ValidateIDToken validates the id token jwt string
// It returns the user extract from the IDTokenCustomClaims
func (s *tokenService) ValidateIDToken(tokenString string) (*model.User, error) {
	claims, err := validateIDToken(tokenString, s.PubKey) // uses public RSA key

	// We'll just return unauthorized error in all instances of failing to verify user
	if err != nil {
		log.Printf("Unable to validate or parse idToken - Error: %v\n", err)
		return nil, apperrors.NewAuthorization("Unable to verify user from idToken")
	}

	return claims.User, nil
}
```

## Create AuthUser Middleware

We can finally create the middleware to extract an authorized user! To do this, let's add a file, `~/handler/middleware/auth_user.go`.

```go
package middleware

// IMPORTS OMITTED - Make sure to import validator/v10
// My auto import always uses V9

type authHeader struct {
	IDToken string `header:"Authorization"`
}

// used to help extract validation errors
type invalidArgument struct {
	Field string `json:"field"`
	Value string `json:"value"`
	Tag   string `json:"tag"`
	Param string `json:"param"`
}

// AuthUser extracts a user from the Authorization header
// which is of the form "Bearer token"
// It sets the user to the context if the user exists
func AuthUser(s model.TokenService) gin.HandlerFunc {
	return func(c *gin.Context) {
		h := authHeader{}

		// bind Authorization Header to h and check for validation errors
		if err := c.ShouldBindHeader(&h); err != nil {
			if errs, ok := err.(validator.ValidationErrors); ok {
				// we used this type in bind_data to extract desired fields from errs
				// you might consider extracting it
				var invalidArgs []invalidArgument

				for _, err := range errs {
					invalidArgs = append(invalidArgs, invalidArgument{
						err.Field(),
						err.Value().(string),
						err.Tag(),
						err.Param(),
					})
				}

				err := apperrors.NewBadRequest("Invalid request parameters. See invalidArgs")

				c.JSON(err.Status(), gin.H{
					"error":       err,
					"invalidArgs": invalidArgs,
				})
				c.Abort()
				return
			}

			// otherwise error type is unknown
			err := apperrors.NewInternal()
			c.JSON(err.Status(), gin.H{
				"error": err,
			})
			c.Abort()
			return
		}

		idTokenHeader := strings.Split(h.IDToken, "Bearer ")

		if len(idTokenHeader) < 2 {
			err := apperrors.NewAuthorization("Must provide Authorization header with format `Bearer {token}`")

			c.JSON(err.Status(), gin.H{
				"error": err,
			})
			c.Abort()
			return
		}

		// validate ID token here
		user, err := s.ValidateIDToken(idTokenHeader[1])

		if err != nil {
			err := apperrors.NewAuthorization("Provided token is invalid")
			c.JSON(err.Status(), gin.H{
				"error": err,
			})
			c.Abort()
			return
		}

		c.Set("user", user)

		c.Next()
	}
}
```

In the middleware we:

1. Check for validation errors provided by Gin's `ShouldBindHeader`. We check if the error is a validation error. If it is, we extract a few fields off of each `validator.ValidationErrors` (individual errors are called `FieldError`) to send to the client as a `BadRequest`. We use `invalidArgument` to define the error fields we want to send to the client. 
2. Check that the Authorization header is provided in the format `Bearer {token}`. We split the token off of the string, which is a stringified JWT, and check for any errors.
3. Finally, we reach out to the `ValidateIDToken` method we just created, which uses the JWT library to make sure the token can be verified with the *public* RSA key, and that the token is not expired. 
4. If all of these cases pass, we can `Set` the user on our Gin context, and call the `Next()` handler.


## Apply AuthUser to Me Handler

Recall that our `Me` handler will expect to extract a *model.User* from the gin context. To make this user available to our handler, we need to add the `AuthUser` middleware to this individual handler.

We can update this in `~/handler/handler.go`.

```go
  // Wish I had thought this through better!
	if gin.Mode() != gin.TestMode {
		g.Use(middleware.Timeout(c.TimeoutDuration, apperrors.NewServiceUnavailable()))
		g.GET("/me", middleware.AuthUser(h.TokenService), h.Me)
	} else {
		g.GET("/me", h.Me)
	}
```

Notice that we only add the middleware if we're not in test mode. That's because we previously created a handler test in isolation of the middleware. You can choose to do it this way, or to integrate middleware and handlers for your test.

I'd really love if any of you have any practical suggestions on how to handle this in production-grade applications!

## Run and Test Application

From the application root, let's run our application!

```sh
docker-compose up
```

### Sign In An Existing User

I'll first sign in an existing user.

*If you want to see how to make this request in Postman, checkout the YouTube video.*

```sh
➜ curl --location --request POST 'http://malcorp.test/api/account/signin' \
--header 'Authorization: Bearer {{idToken}}' \
--header 'Content-Type: application/json' \
--data-raw '{
    "email": "guy01@guy.com",
    "password": "avalidpassword123"
}'
```

The response is:

```json
{
  "tokens": {
    "idToken":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7InVpZCI6ImVlODZjYzJjLTg2ODEtNDU5YS1hOTc3LTNjYWY5NzUzZTE0YiIsImVtYWlsIjoiZ3V5MDFAZ3V5LmNvbSIsIm5hbWUiOiIiLCJpbWFnZVVybCI6IiIsIndlYnNpdGUiOiIifSwiZXhwIjoxNjA5MjY5NDI2LCJpYXQiOjE2MDkyNjg1MjZ9.RLC-RcH-YnSJfKqgbucUvvo2DV3sLcJwXSMlbvOqnEbPgjeWv_3ae61lZU909xUMq6Qrl-tpGLxgkrkk3FiXUuhu8J8bBdCYgSgBhPTkoVuALSuC9N-0mcVTLiQ2zZVwxpuDWHCxHtcjinCwt-XSq94CuSqfwDxjmc--Y0IiQMa5pRMa5Ol4qhs0ABkCI-cq0op8_HUOR7mctmiyR1xaKC8AmvLXbgYp7-g5DfKquYjdEDM640W4y99eBTvDRJwHqRTE5QBVYwzVylqFcy82yCriKPB0sgv60iACOjngkzTqatPzYI6C_QUtKOoaNY1NiIpRI99jiFrrW7z1IIt9NA","refreshToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiJlZTg2Y2MyYy04NjgxLTQ1OWEtYTk3Ny0zY2FmOTc1M2UxNGIiLCJleHAiOjE2MDk1Mjc3MjYsImp0aSI6IjE0NDBlNTg4LWI2NjgtNGVjNy05ZmJiLTU5OTM0ODhjMTE4NCIsImlhdCI6MTYwOTI2ODUyNn0.Hd6j4jzD5IKswvWqnJKG7XFLIBw-IRMLeCD4ojAZedA"
  }
} 
```

We can then add the `idToken` to our "Authorization" header while making a GET request to "/me".

```sh
➜ curl --location --request GET 'http://malcorp.test/api/account/me' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7InVpZCI6ImVlODZjYzJjLTg2ODEtNDU5YS1hOTc3LTNjYWY5NzUzZTE0YiIsImVtYWlsIjoiZ3V5MDFAZ3V5LmNvbSIsIm5hbWUiOiIiLCJpbWFnZVVybCI6IiIsIndlYnNpdGUiOiIifSwiZXhwIjoxNjA5MjY5NDI2LCJpYXQiOjE2MDkyNjg1MjZ9.RLC-RcH-YnSJfKqgbucUvvo2DV3sLcJwXSMlbvOqnEbPgjeWv_3ae61lZU909xUMq6Qrl-tpGLxgkrkk3FiXUuhu8J8bBdCYgSgBhPTkoVuALSuC9N-0mcVTLiQ2zZVwxpuDWHCxHtcjinCwt-XSq94CuSqfwDxjmc--Y0IiQMa5pRMa5Ol4qhs0ABkCI-cq0op8_HUOR7mctmiyR1xaKC8AmvLXbgYp7-g5DfKquYjdEDM640W4y99eBTvDRJwHqRTE5QBVYwzVylqFcy82yCriKPB0sgv60iACOjngkzTqatPzYI6C_QUtKOoaNY1NiIpRI99jiFrrW7z1IIt9NA'
```

And we receive the user as a response.

```json
{
  "user": {
    "uid":"ee86cc2c-8681-459a-a977-3caf9753e14b",
    "email":"guy01@guy.com",
    "name":"",
    "imageUrl":"",
    "website":""
  }
}
```

I recommend you try to send the request with a missing auth header, which should return an HTTP 401 error with a response such as:

```json
{
    "error": {
        "type": "AUTHORIZATION",
        "message": "Must provide Authorization header with format `Bearer {token}`"
    }
}
```

## Conclusion

That's all for today. Once again I'll remind you of the unit tests available in the Github repository! 

Next time, we'll get working on a "tokens" handler which will be used by a client to get renewed id and refresh tokens. This helps clients remain logged in to applications in our company or domain!

Hasta pronto!