We have left off part 2 by creating the blog apis. In this article we will design the User struct and implement User authentication. We will be using JWT token for authentication.
Do make sure you have gone through Part 2 and Part 1.
Note : All codes for this series can be found on this repo. Please checkout to branch
part-3
by git checkout part-3
to get code for this article.
contents
- Create user apis and authentication
- Configure main.go
- API demo
- Wrap up
Create user apis and authentication
Designing user struct
Inside models
folder add a user.go
file with below contents.
package models
import "time"
//User -> User struct to save user on database
type User struct {
ID int64 `gorm:primary_key;auto_increment;json:id`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Email string `json:"email"`
Password string `json:"password"`
IsActive bool `json:"is_active"`
CreatedAt time.Time `json:"created_at", omitempty`
UpdatedAt time.Time `json:"updated_at", omitempty`
}
//TableName -> returns the table name of User Model
func (user *User) TableName() string {
return "user"
}
//UserLogin -> Request Binding for User Login
type UserLogin struct {
Email string `form:"email" binding:"required"`
Password string `form:"password" binding:"required"`
}
//UserRegister -> Request Binding for User Register
type UserRegister struct {
Email string `form:"email" json:"email" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
FirstName string `form:"first_name"`
LastName string `form:"last_name"`
}
//ResponseMap -> response map method of User
func (user *User) ResponseMap() map[string]interface{} {
resp := make(map[string]interface{})
resp["id"] = user.ID
resp["email"] = user.Email
resp["first_name"] = user.FirstName
resp["last_name"] = user.LastName
resp["is_active"] = user.IsActive
resp["created_at"] = user.CreatedAt
resp["updated_at"] = user.UpdatedAt
return resp
}
Hash and compare hashed password
For the security purpose we will encrypt hashed password on database. Execute
go get golang.org/x/crypto/bcrypt
on the command line to install the bcrypt
package. Create util/password.go
file and add the following code.
package util
import "golang.org/x/crypto/bcrypt"
func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
return string(bytes), err
}
func CheckPasswordHash(password string, hash string) error {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err
}
User register and login repositories
Add the api/repository/user.go
file and add the following code.
package repository
import (
"blog/infrastructure"
"blog/models"
"blog/util"
)
//UserRepository -> UserRepository resposible for accessing database
type UserRepository struct {
db infrastructure.Database
}
//NewUserRepository -> creates a instance on UserRepository
func NewUserRepository(db infrastructure.Database) UserRepository {
return UserRepository{
db: db,
}
}
//CreateUser -> method for saving user to database
func (u UserRepository) CreateUser(user models.UserRegister) error {
var dbUser models.User
dbUser.Email = user.Email
dbUser.FirstName = user.FirstName
dbUser.LastName = user.LastName
dbUser.Password = user.Password
dbUser.IsActive = true
return u.db.DB.Create(&dbUser).Error
}
//LoginUser -> method for returning user
func (u UserRepository) LoginUser(user models.UserLogin) (*models.User, error) {
var dbUser models.User
email := user.Email
password := user.Password
err := u.db.DB.Where("email = ?", email).First(&dbUser).Error
if err != nil {
return nil, err
}
hashErr := util.CheckPasswordHash(password, dbUser.Password)
if hashErr != nil {
return nil, hashErr
}
return &dbUser, nil
}
Here dbUser.IsActive = true
this might seems a little bit off ? No, this is on purpose for now. Later on upcoming part we will implement User confirmation via email.
User register and login services
Add the api/service/user.go
file and add the following code.
package service
import (
"blog/api/repository"
"blog/models"
)
//UserService UserService struct
type UserService struct {
repo repository.UserRepository
}
//NewUserService : get injected user repo
func NewUserService(repo repository.UserRepository) UserService {
return UserService{
repo: repo,
}
}
//Save -> saves users entity
func (u UserService) CreateUser(user models.UserRegister) error {
return u.repo.CreateUser(user)
}
//Login -> Gets validated user
func (u UserService) LoginUser(user models.UserLogin) (*models.User, error) {
return u.repo.LoginUser(user)
}
User register and login controllers
Install jwt
package by go get github.com/golang-jwt/jwt
this package will be used to issue JWT Token. Add the api/controller/user.go
file and add the following.
package controller
import (
"blog/api/service"
"blog/models"
"blog/util"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt"
)
//UserController struct
type UserController struct {
service service.UserService
}
//NewUserController : NewUserController
func NewUserController(s service.UserService) UserController {
return UserController{
service: s,
}
}
//CreateUser -> calls CreateUser services for validated user
func (u *UserController) CreateUser(c *gin.Context) {
var user models.UserRegister
if err := c.ShouldBind(&user); err != nil {
util.ErrorJSON(c, http.StatusBadRequest, "Inavlid Json Provided")
return
}
hashPassword, _ := util.HashPassword(user.Password)
user.Password = hashPassword
err := u.service.CreateUser(user)
if err != nil {
util.ErrorJSON(c, http.StatusBadRequest, "Failed to create user")
return
}
util.SuccessJSON(c, http.StatusOK, "Successfully Created user")
}
//LoginUser : Generates JWT Token for validated user
func (u *UserController) Login(c *gin.Context) {
var user models.UserLogin
var hmacSampleSecret []byte
if err := c.ShouldBindJSON(&user); err != nil {
util.ErrorJSON(c, http.StatusBadRequest, "Inavlid Json Provided")
return
}
dbUser, err := u.service.LoginUser(user)
if err != nil {
util.ErrorJSON(c, http.StatusBadRequest, "Invalid Login Credentials")
return
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"user": dbUser,
"exp": time.Now().Add(time.Minute * 15).Unix(),
})
tokenString, err := token.SignedString(hmacSampleSecret)
if err != nil {
util.ErrorJSON(c, http.StatusBadRequest, "Failed to get token")
return
}
response := &util.Response{
Success: true,
Message: "Token generated sucessfully",
Data: tokenString,
}
c.JSON(http.StatusOK, response)
}
User register and login routes
Finally, we have come to the part where we can expose the register and login apis.
Add the following code to api/routes/user.go
.
package routes
import (
"blog/api/controller"
"blog/infrastructure"
)
//UserRoute -> Route for user module
type UserRoute struct {
Handler infrastructure.GinRouter
Controller controller.UserController
}
//NewUserRoute -> initializes new instance of UserRoute
func NewUserRoute(
controller controller.UserController,
handler infrastructure.GinRouter,
) UserRoute {
return UserRoute{
Handler: handler,
Controller: controller,
}
}
//Setup -> setups user routes
func (u UserRoute) Setup() {
user := u.Handler.Gin.Group("/auth")
{
user.POST("/register", u.Controller.CreateUser)
user.POST("/login", u.Controller.LoginUser)
}
}
The above code will configure user authentication routes register
and login
.
Configure main.go
Now, final piece of code is to update main.go
file, gluing all things together.
package main
import (
"blog/api/controller"
"blog/api/repository"
"blog/api/routes"
"blog/api/service"
"blog/infrastructure"
"blog/models"
)
func init() {
infrastructure.LoadEnv()
}
func main() {
router := infrastructure.NewGinRouter()
db := infrastructure.NewDatabase()
postRepository := repository.NewPostRepository(db)
postService := service.NewPostService(postRepository)
postController := controller.NewPostController(postService)
postRoute := routes.NewPostRoute(postController, router)
postRoute.Setup()
// add up these
userRepository := repository.NewUserRepository(db)
userService := service.NewUserService(userRepository)
userController := controller.NewUserController(userService)
userRoute := routes.NewUserRoute(userController, router)
userRoute.Setup()
db.DB.AutoMigrate(&models.Post{}, &models.User{})
router.Gin.Run(":8000")
}
API demo
It's time to spin up the server and test out register
and login
apis.
docker-compose up --build
Now, Bring up your favorite API Tester application. I will be using postman
Testing register
endpoint -> /auth/register
Testing login
endpoint -> /auth/login
verify token on jwt.io
Wrap Up
Next up, Upcoming Part 4 will cover the following :
- Adding user to blog
- Apis of related user posts
- and many more
Important Links :
- Link to part 2 : Blog with Go, Gin, MySQL and Docker Part 2
- Link to part 1 : Blog with Go, Gin, MySQL and Docker Part 1
- Repository Link: Github
- Read about Clean Architecture: Clean Architecture
Thanks a lot! A feedback is really appreciated. I am on Linkedin. Let's connect.
Happy reading !!!