DEV Community

ShellRean
ShellRean

Posted on • Edited on

Golang: Create Authentication JWT #1

After all this time we learned Golang which is a simple application application, finally we want to make a complete application but we are stuck with the question, how do we authenticate in Golang? That was also my question when I decided to move from node-js to golang.

Folder structure introduction

In this tutorial we will use the folder structure that I usually use
├───app
├───domain
├───entities
│ └───helper
├───interface
│ └───http
└───services
└───user
├───repository
│ └───postgres
└───usecase
app: contains main.package which will be executed the first time the go program is run.
domain: contains the struct domain
entities/helper: contains helper functions
interface/http: contains the gin gonic routing handler
service: contains service
usecase: where to put usecase instead of the user.
repository: where to put the repository

now run go mod init shellrean.com/auth in the created folder.

Create a table in postgresql

For our DBMS, this time using PostgreSQL, create a table with the names of users with the following specifications:

  • id (int) auto increment
  • name (string)
  • email (string)
  • password (string)
  • created_at (timestamp)
  • updated_at (timestamp) Then create 1 dummy user, make it free to generate passwords from bcrypt using the online tool at Bcrypt-Generator ## Create domain files create user.go file and save it in the domain folder, we will create a domain struct and user interface in this file.
package domain

import (
    "time"
    "context"    
)

type User struct {
    ID          int64       `json:"id"`
    Name        string      `json:"name" validate:"required"`
    Email       string      `json:"email" validate:"required,email"`
    Password    string      `json:"password" validate:"required,min=6"`
    CreatedAt   time.Time   `json:"created_at"`
    UpdatedAt   time.Time   `json:"updated_at"`
}

type UserUsecase interface {
    Authentication(ctx context.Context, ur DTOUserLoginRequest) (DTOTokenResponse, error)
    RefreshToken(ctx context.Context, ur DTOTokenResponse) (DTOTokenResponse, error)
}
type UserRepository interface {
    GetByID(ctx context.Context, id int64) (User, error)
    GetByEmail(ctx context.Context, email string) (User, error)
}

Enter fullscreen mode Exit fullscreen mode

create a domain.go file then save it in the same folder, we will make a domain struck for the token in this file

package domain

type TokenDetails struct {
    AccessToken     string
    RefreshToken            string
    AtExpires       int64
    RtExpires       int64
}
Enter fullscreen mode Exit fullscreen mode

Create a file repository

create main_user.go file and save it in the services/user/repository/postgres folder, we will create a repository function for retrieving data from the database.

package postgres

import (
    "context"
    "database/sql"

    "shellrean.com/auth/domain"
)

type postgresUserRepository struct {
    Conn *sql.DB
}

func NewPostgresUserRepository(Conn *sql.DB) domain.UserRepository {
    return &postgresUserRepository{
        Conn,
    }
}

func (m *postgresUserRepository) GetByEmail(ctx context.Context, email string) (u domain.User, err error) {
    query := `SELECT id,name,email,password,created_at,updated_at
            FROM users WHERE email=$1`

    err = m.Conn.QueryRowContext(ctx, query,email).
            Scan(&u.ID,&t.Name,&t.Email,&t.Password,&t.CreatedAt,&t.UpdatedAt)
    if err != nil {
        return err
    }

    return
}
Enter fullscreen mode Exit fullscreen mode

Create a helper file

create token.go file and save it in the entities /helper folder, we will create a helper function that will perform the operation with JWT

package helper

import (
    "strings"
    "time"

    "github.com/dgrijalva/jwt-go"

    "shellrean.com/auth/domain"
)

func GenerateTokenDetail(td *domain.TokenDetails) {
    td.AtExpires = time.Now().Add(time.Minute * 15).Unix()
    td.RtExpires = time.Now().Add(time.Hour * 24 * 7).Unix()
}

func CreateAccessToken(key string, user domain.User, td *domain.TokenDetails) (err error) {
    atClaims := jwt.MapClaims{}
    atClaims["authorized"] = true
    atClaims["user_id"] = user.ID
    atClaims["exp"] = td.AtExpires

    at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims)
    td.AccessToken, err = at.SignedString([]byte(key))
    if err != nil {
        return domain.ErrSessDecode
    }
    return
}

func ExtractToken(bearer string) (res string) {
    str := strings.Split(bearer, " ")
    if len(str) == 2 {
        res = str[1]
        return
    }
    return
}

func VerifyToken(key string, tokenString string) (*jwt.Token, error) {
    token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
        if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
            return nil, domain.ErrSessDecode
        }
        return []byte(key), nil
    })
    if err != nil {
        return nil, domain.ErrSessVerifation
    }
    return token, nil
}

func TokenValid(token *jwt.Token) error {
    if _, ok := token.Claims.(jwt.Claims); !ok || !token.Valid {
        return domain.ErrSessInvalid
    }
    return nil
}

func ExtractTokenMetadata(token *jwt.Token) map[string]interface{}{
    claims, ok := token.Claims.(jwt.MapClaims)
    if ok && token.Valid {
        return claims
    }
    return nil
}
Enter fullscreen mode Exit fullscreen mode

At this point, we have created 2 domain files, 1 repository file and 1 helper file, we will continue in the next article.

Top comments (0)