Implement login user API that returns PASETO or JWT access token in Go
Tags: go, tutorial, webdev, beginners
Published: 2021-04-24 08:13:04

Hello everyone! Welcome back to the backend master class!

In the [previous lecture](https://dev.to/techschoolguru/how-to-create-and-verify-jwt-paseto-token-in-golang-1l5j), we’ve implemented the token maker interface using JWT and PASETO. It provides 2 methods to create and verify tokens.

So today we’re gonna learn how to use it to implement the login API, where the username and password are provided by the client, and the server will return an access token if those credentials are correct.

{% youtube lnHbZ9GOGAs %}

Here's:

- Link to the [full series playlist on Youtube](https://bit.ly/backendmaster)
- And its [Github repository](https://github.com/techschool/simplebank)

OK let’s start!

## Add token Maker to the Server

The first step is to add the token maker to our API server. So let’s open `api/server.go` file!

In the `Server` struct, I’m gonna add a `tokenMaker` field of type `token.Maker` interface.

```go
type Server struct {
    store      db.Store
    tokenMaker token.Maker
    router     *gin.Engine
}
```

Then let’s initialize this field inside the `NewServer()` function! First we have to create a new token maker object. We can choose to use either `JWT` or `PASETO`, they both implement the same `token.Maker` interface.

I think PASETO is better, so let’s call `token.NewPasetoMaker()`. It requires a symmetric key string, so we will need to load this from environment variable. For now, let’s just put an empty string here as a placeholder.

If the returned error is not `nil`, we return a `nil` server, and an error saying "cannot create token maker". The `%w` is used to wrap the original error.

```go
func NewServer(store db.Store) (*Server, error) {
    tokenMaker, err := token.NewPasetoMaker("")
    if err != nil {
        return nil, fmt.Errorf("cannot create token maker: %w", err)
    }

    server := &Server{
        store:      store,
        tokenMaker: tokenMaker,
    }

    ...

    return server, nil
}
```

OK, so now we have to change the return type of the `NewServer()` function to include an `error` as well. Then in the statement to create a `Server` object, we add the `tokenMaker` object that we’ve just created.

Alright, now let’s come back to the symmetric key parameter. I’m gonna add a new environment variable to the `app.env` file. Let’s call it `TOKEN_SYMMETRIC_KEY`.

And as we’re using PASETO version 2, which uses ChachaPoly algorithm, the size of this symmetric key should be exactly 32 bytes.

```env
TOKEN_SYMMETRIC_KEY=12345678901234567890123456789012
ACCESS_TOKEN_DURATION=15m
```

We should also add 1 more variable to store the valid duration of the access token. It’s a best practice to set this to a very short duration, let’s say, just 15 minutes for example.

OK, now we have to update our config struct to include the 2 new variables that we’ve just added.

First, the `TokenSymmetricKey` of type `string`. We have to specify the `mapstructure` tag for it because [viper](https://github.com/spf13/viper) uses [mapstructure](https://github.com/mitchellh/mapstructure) package to parse the config data. Please refer to the [lecture 12 of the course](https://dev.to/techschoolguru/load-config-from-file-environment-variables-in-golang-with-viper-2j2d) if you don’t know how to use viper.

```go
type Config struct {
    ...
    TokenSymmetricKey   string        `mapstructure:"TOKEN_SYMMETRIC_KEY"`
    AccessTokenDuration time.Duration `mapstructure:"ACCESS_TOKEN_DURATION"`
}
```

The next field is `AccessTokenDuration` of type `time.Duration`. And its `mapstructure` tag should be this environment variable’s name: `ACCESS_TOKEN_DURATION`.

As you can see, when the type of a config field is `time.Duration`, we can specify the value in a human readable format like this: `15m`.

OK so now we’ve loaded the secret key and token duration into the config, let’s go back to the server and use them. We have to add a `config` parameter to the `NewServer()` function. Then in the `token.NewPasetoMaker()` call, we pass in `config.TokenSymmetricKey`.

We should also add a config field to the `Server` struct, and store it here when initialize the `Server` object. We will use the `TokenDuration` in this `config` object later when creating the tokens.

```go
type Server struct {
    config     util.Config
    store      db.Store
    tokenMaker token.Maker
    router     *gin.Engine
}

func NewServer(config util.Config, store db.Store) (*Server, error) {
    tokenMaker, err := token.NewPasetoMaker(config.TokenSymmetricKey)
    if err != nil {
        return nil, fmt.Errorf("cannot create token maker: %w", err)
    }

    server := &Server{
        config:     config,
        store:      store,
        tokenMaker: tokenMaker,
    }

    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        v.RegisterValidation("currency", validCurrency)
    }

    server.setupRouter()
    return server, nil
}
```

At the end of this function, we should return a nil error. And that will be it!

However, as we added a new config parameter to the `NewServer()` function, some unit tests that we wrote before are broken. So let’s fix them!

## Fix broken unit tests

In the `api/main_test.go` file, I’m gonna define a function `newTestServer()`  that will create a new server for test. It takes a `testing.T` object and a `db.Store` interface as input. And it will return a `Server` object as output.

In this function, let’s create a new `config` object, with `TokenSymmetricKey` is `util.RandomString` of 32 characters, and `AccessTokenDuration` is 1 minute.

```go
func newTestServer(t *testing.T, store db.Store) *Server {
    config := util.Config{
        TokenSymmetricKey:   util.RandomString(32),
        AccessTokenDuration: time.Minute,
    }

    server, err := NewServer(config, store)
    require.NoError(t, err)

    return server
}
```

Then we create a new server with that `config` object and the input `store` interface. Require no errors, and finally return the created `server`.

Now get back to the `api/transfer_test.go` file. Here, instead of `NewServer()`, we will call `newTestServer`, and pass in the `testing.T` object and the mock store.

```go
func TestTransferAPI(t *testing.T) {
    ...

    for i := range testCases {
        tc := testCases[i]

        t.Run(tc.name, func(t *testing.T) {
            ...

            server := newTestServer(t, store)
            recorder := httptest.NewRecorder()

            ...
        })
    }
}
```

We do the same for the `server` inside `api/user_test.go` file and `api/account_test.go` file as well. There are several calls of `NewServer()` in these files, so we have to change all of them to `newTestServer()`.

Alright, now everything is updated. Let’s run the whole api package tests!

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nj5xykkfqwc2bm2gmus3.png)

All passed! Excellent! So the tests are now working well with the new `Server` struct.

But there’s one more place we need to update, that’s the main entry point of our server: `main.go`

```go
func main() {
    config, err := util.LoadConfig(".")
    if err != nil {
        log.Fatal("cannot load config:", err)
    }

    conn, err := sql.Open(config.DBDriver, config.DBSource)
    if err != nil {
        log.Fatal("cannot connect to db:", err)
    }

    store := db.NewStore(conn)
    server, err := api.NewServer(config, store)
    if err != nil {
        log.Fatal("cannot create server:", err)
    }

    err = server.Start(config.ServerAddress)
    if err != nil {
        log.Fatal("cannot start server:", err)
    }
}
```

Here, in this `main()` function, we have to add config to the `api.NewServer()` call. And this call will return a `server` and an `error`.

If `error` is not `nil`, we just write a fatal log, saying "cannot create server". Just like that, and we’re done!

Now it’s time to build the login user API!

## Implement login user handler

Let’s open the `api/user.go` file!

The login API’s request payload must contain the `username` and `password`, which is very similar to the `createUserRequest`:

```go
type createUserRequest struct {
    Username string `json:"username" binding:"required,alphanum"`
    Password string `json:"password" binding:"required,min=6"`
    FullName string `json:"full_name" binding:"required"`
    Email    string `json:"email" binding:"required,email"`
}
```

So I’m gonna copy this struct, and paste it to the end of this file. Then let’s change the struct name to `loginUserRequest` and remove the `FullName` and `Email` fields, just keep the `Username` and `Password` fields.

```go
type loginUserRequest struct {
    Username string `json:"username" binding:"required,alphanum"`
    Password string `json:"password" binding:"required,min=6"`
}
```

Next, let’s define the `loginUserResponse` struct. The most important field that should be returned to the client is `AccessToken` string. This is the token that we will create using the token maker interface.

```go
type loginUserResponse struct {
    AccessToken string       `json:"access_token"`
}
```

Beside the access token, we might also want to return some information of the logged in user, just like the one we returned in the create user API:

```go
type createUserResponse struct {
    Username          string    `json:"username"`
    FullName          string    `json:"full_name"`
    Email             string    `json:"email"`
    PasswordChangedAt time.Time `json:"password_changed_at"`
    CreatedAt         time.Time `json:"created_at"`
}
```

So to make this struct reusable, I’m gonna change its name to just `userResponse`. It will be the type of the `User` field in this `loginUserResponse` struct:

```go
type userResponse struct {
    Username          string    `json:"username"`
    FullName          string    `json:"full_name"`
    Email             string    `json:"email"`
    PasswordChangedAt time.Time `json:"password_changed_at"`
    CreatedAt         time.Time `json:"created_at"`
}

type loginUserResponse struct {
    AccessToken string       `json:"access_token"`
    User        userResponse `json:"user"`
}
```

Then let’s copy the `userResponse` object from the `createUser()` handler, and define a `newUserResponse()` function at the top.

```go
func newUserResponse(user db.User) userResponse {
    return userResponse{
        Username:          user.Username,
        FullName:          user.FullName,
        Email:             user.Email,
        PasswordChangedAt: user.PasswordChangedAt,
        CreatedAt:         user.CreatedAt,
    }
}
```

The role of this function is to convert the input `db.User` object into `userResponse`. The reason we do that is because there’s a sensitive data inside the `db.User` struct, which is the `hashed_password`, that we don’t want to expose to the client.

OK, so now in the `createUser()` handler, we can just call the `newUserResponse()` function to create the response object.

```go
func (server *Server) createUser(ctx *gin.Context) {
    ...

    user, err := server.store.CreateUser(ctx, arg)
    ...

    rsp := newUserResponse(user)
    ctx.JSON(http.StatusOK, rsp)
}
```

The `newUserResponse()` function will be useful for our new `loginUser()` handler as well.

Alright, now let’s add a new method to the server struct: `loginUser()`. Similar as in other API handlers, this function will take a `gin.Context` object as input.

Inside, we declare a `request` object of type `loginUserRequest`, and we call the `ctx.ShouldBindJSON()` function with a pointer to that `request` object. This will bind all the input parameters of the API into the `request` object.

```go
func (server *Server) loginUser(ctx *gin.Context) {
    var req loginUserRequest
    if err := ctx.ShouldBindJSON(&req); err != nil {
        ctx.JSON(http.StatusBadRequest, errorResponse(err))
        return
    }

    ...
}
```

If `error` is not `nil`, we send a response with status `400 Bad Request` to the client, together with the `errorResponse()` body to explain why it failed.

If there’s no error, we will find the user from the database by calling `server.store.GetUser()` with the context `ctx` and `req.Username`.

```go
func (server *Server) loginUser(ctx *gin.Context) {
    ...

    user, err := server.store.GetUser(ctx, req.Username)
    if err != nil {
        if err == sql.ErrNoRows {
            ctx.JSON(http.StatusNotFound, errorResponse(err))
            return
        }
        ctx.JSON(http.StatusInternalServerError, errorResponse(err))
        return
    }

    ...
}
```

If the error returned by this call is not `nil`, then there are 2 possible cases:

- The first case is when the `username` doesn’t exist, which means `error` equals to `sql.ErrNoRows`. In this case, we send a response with status `404 Not Found` to the client, and return immediately.
- The second case is an unexpected error occurs when talking to the database. In this case, we send a `500 Internal Server Error` status to the client, and also return right away.

If everything goes well, and no errors occur, we will have to check if the password provided by the client is correct or not. So we call `util.CheckPassword()` with the input `req.Password` and `user.HashedPassword`.

```go
func (server *Server) loginUser(ctx *gin.Context) {
    ...

    err = util.CheckPassword(req.Password, user.HashedPassword)
    if err != nil {
        ctx.JSON(http.StatusUnauthorized, errorResponse(err))
        return
    }

    ...
}
```

If this function returns a not `nil` error, then it means the provided password is incorrect. We will send a response with status `401 Unauthorized` to the client, and return.

Only when the password is correct, then we will create a new access token for this user.

Let’s call `server.tokenMaker.CreateToken()`, pass in `user.Username`, and `server.config.AccessTokenDuration` as input arguments.

```go
func (server *Server) loginUser(ctx *gin.Context) {
    ...

    accessToken, err := server.tokenMaker.CreateToken(
        user.Username,
        server.config.AccessTokenDuration,
    )
    if err != nil {
        ctx.JSON(http.StatusInternalServerError, errorResponse(err))
        return
    }

    rsp := loginUserResponse{
        AccessToken: accessToken,
        User:        newUserResponse(user),
    }
    ctx.JSON(http.StatusOK, rsp)
}
```

If an unexpected error occurs, we just return `500 Internal Server Error` status code.

Otherwise, we will build the `loginUserResponse` object, where `AccessToken` is the created access token, and `User` is `newUserResponse(user)`. We then send this response to the client with a `200 OK` status code.

And that’s basically it! The `loginUser()` handler function is completed:

```go
func (server *Server) loginUser(ctx *gin.Context) {
    var req loginUserRequest
    if err := ctx.ShouldBindJSON(&req); err != nil {
        ctx.JSON(http.StatusBadRequest, errorResponse(err))
        return
    }

    user, err := server.store.GetUser(ctx, req.Username)
    if err != nil {
        if err == sql.ErrNoRows {
            ctx.JSON(http.StatusNotFound, errorResponse(err))
            return
        }
        ctx.JSON(http.StatusInternalServerError, errorResponse(err))
        return
    }

    err = util.CheckPassword(req.Password, user.HashedPassword)
    if err != nil {
        ctx.JSON(http.StatusUnauthorized, errorResponse(err))
        return
    }

    accessToken, err := server.tokenMaker.CreateToken(
        user.Username,
        server.config.AccessTokenDuration,
    )
    if err != nil {
        ctx.JSON(http.StatusInternalServerError, errorResponse(err))
        return
    }

    rsp := loginUserResponse{
        AccessToken: accessToken,
        User:        newUserResponse(user),
    }
    ctx.JSON(http.StatusOK, rsp)
}
```

## Add login API route to the server

The next step is to add a new API endpoint to the server that will route the login request to the `loginUser()` handler.

I’m gonna put it next to the create user route. So `router.POST()`, the path should be `/users/login`, and the handler function is `server.loginUser()`.

```go
func NewServer(config util.Config, store db.Store) (*Server, error) {
    ...

    router := gin.Default()

    router.POST("/users", server.createUser)
    router.POST("/users/login", server.loginUser)

    ...
}
```

And we’re done!

However, this `NewServer()` function is getting quite long now, which makes it harder to read.

So I’m gonna split the routing part into a separate method of the `server` struct. Let’s call it `setupRouter()`. Then paste in all the routing codes.

```go
func (server *Server) setupRouter() {
    router := gin.Default()

    router.POST("/users", server.createUser)
    router.POST("/users/login", server.loginUser)

    router.POST("/accounts", server.createAccount)
    router.GET("/accounts/:id", server.getAccount)
    router.GET("/accounts", server.listAccounts)

    router.POST("/transfers", server.createTransfer)

    server.router = router
}
```

We should move the gin router variable here as well. And at the end, we should save this router to the `server.router` field.

Then all we have to do in the `NewServer()` function is to call `server.setupRouter()`.

```go
func NewServer(config util.Config, store db.Store) (*Server, error) {
    tokenMaker, err := token.NewPasetoMaker(config.TokenSymmetricKey)
    if err != nil {
        return nil, fmt.Errorf("cannot create token maker: %w", err)
    }

    server := &Server{
        config:     config,
        store:      store,
        tokenMaker: tokenMaker,
    }

    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        v.RegisterValidation("currency", validCurrency)
    }

    server.setupRouter()
    return server, nil
}

```

And now we’ve really completed the login user API’s implementation. It’s pretty easy and straightforward, isn’t it?

## Run the server and send login user request

Let’s run the server and send some requests to see how it goes!

```bash
❯ make server
go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] POST   /users                    --> github.com/techschool/simplebank/api.(*Server).createUser-fm (3 handlers)
[GIN-debug] POST   /users/login              --> github.com/techschool/simplebank/api.(*Server).loginUser-fm (3 handlers)
[GIN-debug] POST   /accounts                 --> github.com/techschool/simplebank/api.(*Server).createAccount-fm (4 handlers)
[GIN-debug] GET    /accounts/:id             --> github.com/techschool/simplebank/api.(*Server).getAccount-fm (4 handlers)
[GIN-debug] GET    /accounts                 --> github.com/techschool/simplebank/api.(*Server).listAccounts-fm (4 handlers)
[GIN-debug] POST   /transfers                --> github.com/techschool/simplebank/api.(*Server).createTransfer-fm (4 handlers)
[GIN-debug] Listening and serving HTTP on 0.0.0.0:8080
```

As you can see here, the login user API is up and running.

Now I’m gonna open [Postman](https://www.postman.com/), create a new request and set it method to `POST`. The URL should be <http://localhost:8080/users/login>, then select body, raw, and JSON format.

The JSON body will have 2 fields: `username` and `password`. In the database, there are 4 users that we already created in previous lectures. So I’m gonna use the first user with username `quang1` and the password is `secret`.

OK let’s send this request:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vwyei97ur9h1d1njvxsp.png)

Voilà! It's successful!

We’ve got the PASETO v2 local access token here. And all the information of the logged in user in this object. So it worked!

Let’s try login with an invalid password: `xyz`. Send the request.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d4nvc3rurrsflbwxda5j.png)

Now we’ve got `400 Bad Request` because the password we sent was too short. That's because we have a validation rule for the password field to have at least 6 characters:

```go
type loginUserRequest struct {
    ...
    Password string `json:"password" binding:"required,min=6"`
}
```

So let’s change this value to `xyz123`. And send the request again.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v1i2t9o4wahc6euo9w0v.png)

This time we’ve got `401 Unauthorized` status code, and the error is: "hashed password is not the hash of the given password", or in other words, the provided password is incorrect.

Now let’s try the case when username doesn’t exist. I’m gonna change the `username` to `quang10`, and send the request again.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/613iweotcvnxahc0pvu5.png)

This time, we’ve got `404 Not Found` status code. That’s exactly what we expected! So the login user API is working very well.

Before we finish, I’m gonna show you how easy it is to change the token types.

## Change the token type

Right now, we’re using `PASETO`, but since it implements the same token maker interface with JWT, it will be super easy if we want to switch to `JWT`.

All we have to do is just change the `token.NewPasetoMaker()` call to `token.NewJWTMaker()` in the `api/server.go` file:

```go
func NewServer(config util.Config, store db.Store) (*Server, error) {
    tokenMaker, err := token.NewJWTMaker(config.TokenSymmetricKey)
    if err != nil {
        return nil, fmt.Errorf("cannot create token maker: %w", err)
    }

    ...
}
```

That’s it! Let’s restart the server, then go back to Postman and send the login request one more time.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x85z3ngc9foi1scy3jrc.png)

As you can see, the request is successful. And now the access token looks different because it’s a JWT token, not a PASETO token as before.

OK, now as we’ve confirmed that it worked, I’m gonna revert the token type to PASETO because it’s better than JWT in my opinion.

```go
func NewServer(config util.Config, store db.Store) (*Server, error) {
    tokenMaker, err := token.NewPasetoMaker(config.TokenSymmetricKey)
    if err != nil {
        return nil, fmt.Errorf("cannot create token maker: %w", err)
    }

    ...
}
```

And that wraps up this lecture about implementing login user API in Go.

I hope you find it useful. Thanks a lot for reading, and see you soon in the next one!

---

If you like the article, please subscribe to our [Youtube channel](http://bit.ly/techschoolguru) and follow us on [Twitter](https://twitter.com/techschoolguru) or [Facebook](https://www.facebook.com/techschool.guru) for more tutorials in the future.

---
If you want to join me on my current amazing team at Voodoo, check out [our job openings here](https://bit.ly/voodoojobs). Remote or onsite in Paris/Amsterdam/London/Berlin/Barcelona with visa sponsorship.