Creating Login and Private Routes with Refreshing tokens - Part[3/3] of Go Authentication series
Tags: go, security, beginners
Published: 2020-10-31 06:52:06

In this part, we will create 3 routes:
- A SignIn route from where users can log in either with their username or email along with their password.
- A secure Route from where any logged-in user can get their details from the database.
- Another route that returns an access token iff the refresh token is valid.

So let's get started:

We will make all these functions inside the _router/user.go_ file.

First, we will modify the _SetupUserRoutes_ function inside _router/user.go_ file.

```go
// SetupUserRoutes func sets up all the user routes
func SetupUserRoutes() {
	USER.Post("/signup", CreateUser)              // Sign Up a user
++	USER.Post("/signin", LoginUser)               // Sign In a user
++	USER.Get("/get-access-token", GetAccessToken) // returns a new access_token

++	// privUser handles all the private user routes that requires authentication
++	privUser := USER.Group("/private")
++	privUser.Use(util.SecureAuth()) // middleware to secure all routes for this group
++	privUser.Get("/user", GetUserData)
}
```

You can see that we are using our SecureAuth middleware for the private routes here, to make sure that every route from this group is secured. 

### Create a SignIn route 
Now we will create a new function inside _routr/user.go_ file that will handle the logging in for any user.

We will follow the following steps to log in a user:

1. First, we will parse the input data into an input struct.
2. Then, we will check whether or not any user exists according to the provided email or username.
3. If it exists, then we will compare the password provided with the hashed password inside the database.
4. If there is a match, we will then generate access and refresh tokens and set their cookies.
5. Then we return the tokens.

So if we follow all these steps, our function will look like this:

```go
// LoginUser route logins a user in the app
func LoginUser(c *fiber.Ctx) error {
	type LoginInput struct {
		Identity string `json:"identity"`
		Password string `json:"password"`
	}

	input := new(LoginInput)

	if err := c.BodyParser(input); err != nil {
		return c.JSON(fiber.Map{"error": true, "input": "Please review your input"})
	}

	// check if a user exists
	u := new(models.User)
	if res := db.DB.Where(
		&models.User{Email: input.Identity}).Or(
		&models.User{Username: input.Identity},
	).First(&u); res.RowsAffected <= 0 {
		return c.JSON(fiber.Map{"error": true, "general": "Invalid Credentials."})
	}

	// Comparing the password with the hash
	if err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(input.Password)); err != nil {
		return c.JSON(fiber.Map{"error": true, "general": "Invalid Credentials."})
	}

	// setting up the authorization cookies
	accessToken, refreshToken := util.GenerateTokens(u.UUID.String())
	accessCookie, refreshCookie := util.GetAuthCookies(accessToken, refreshToken)
	c.Cookie(accessCookie)
	c.Cookie(refreshCookie)

	return c.Status(fiber.StatusOK).JSON(fiber.Map{
		"access_token":  accessToken,
		"refresh_token": refreshToken,
	})
}
```

Yay!, we have created both a SignUp route and a SignIn route successfully. But the work is still not over, we will now create a secure route from which a logged-in user can access their data.

### Access user data with a secure route
Now, we will create a new function called GetUserData inside the _router/user.go_ file. This function is only triggered when the request passes through our SecureAuth middleware successfully.

```go
// GetUserData returns the details of the user signed in
func GetUserData(c *fiber.Ctx) error {
	id := c.Locals("id")

	u := new(models.User)
	if res := db.DB.Where("uuid = ?", id).First(&u); res.RowsAffected <= 0 {
		return c.JSON(fiber.Map{"error": true, "general": "Cannot find the User"})
	}

	return c.JSON(u)
}
```
### Refreshing Tokens
We know that each of our access tokens expires in 15 minutes. But we don't want our user to log in again and again after 15 minutes.
So this is where the refresh token comes into play. We need to refresh these access tokens. We can do this with the use of refresh tokens we made earlier. So now we have to make a new function called _GetAccessToken_ that will refresh these access tokens.

This function will follow the following steps:
1. Fetch the refresh token from the cookies of the request.
2. Parse the refresh token inside a Claim.
3. Check if the database contains the token belonging to that particular user.
4. If the token exists in the database, validate that token.
5. If the token is valid then generate a new access token and create a new cookie for that access token and return it.

So after following the about steps, our function will look like this:

```go
// GetAccessToken generates and sends a new access token iff there is a valid refresh token
func GetAccessToken(c *fiber.Ctx) error {
	refreshToken := c.Cookies("refresh_token")

	refreshClaims := new(models.Claims)
	token, _ := jwt.ParseWithClaims(refreshToken, refreshClaims,
		func(token *jwt.Token) (interface{}, error) {
			return jwtKey, nil
		})

	if res := db.DB.Where(
		"expires_at = ? AND issued_at = ? AND issuer = ?",
		refreshClaims.ExpiresAt, refreshClaims.IssuedAt, refreshClaims.Issuer,
	).First(&models.Claims{}); res.RowsAffected <= 0 {
		// no such refresh token exist in the database
		c.ClearCookie("access_token", "refresh_token")
		return c.SendStatus(fiber.StatusForbidden)
	}

	if token.Valid {
		if refreshClaims.ExpiresAt < time.Now().Unix() {
			// refresh token is expired
			c.ClearCookie("access_token", "refresh_token")
			return c.SendStatus(fiber.StatusForbidden)
		}
	} else {
		// malformed refresh token
		c.ClearCookie("access_token", "refresh_token")
		return c.SendStatus(fiber.StatusForbidden)
	}

	_, accessToken := util.GenerateAccessClaims(refreshClaims.Issuer)

	c.Cookie(&fiber.Cookie{
		Name:     "access_token",
		Value:    accessToken,
		Expires:  time.Now().Add(24 * time.Hour),
		HTTPOnly: true,
		Secure:   true,
	})

	return c.JSON(fiber.Map{"access_token": accessToken})
}
```

So now our Go Authentication Boilerplate is complete. If you want to take a look at the source code, [here's a GitHub Repository for that](https://github.com/mdfaizan7/go-authentication-boilerplate). If you like it, please give it a :star:.

## Conclusion
So this is it folks! This was my first experience writing a blog and giving back to a community that has given me so much more. This was a 3 Part series and I really enjoyed writing it. 

_Thanks for reading! If you liked this article, please let me know and share it!_