What we are going to do???
So basically we are going to implement user login/registration with user user authentication using jwt and as well as some crud operations on user addresses and user contacts. Basically it's a contact and user profile manager application. So in the following image you can see the folder structure of our project. You can either follow it or you can do what ever folder structure you want. But I highly recommend to use the given folder structure to prevent unnecessary errors and issues.
What is REST?
REST stands for Representational State Transfer, it is the mechanism used by modern client apps to communicate with databases and servers via http.
Prerequisites
- gorilla/mux — A powerful URL router and dispatcher. We use this package to match URL paths with their handlers.
- jinzhu/gorm — The fantastic ORM library for Golang, aims to be developer friendly. We use this ORM(Object relational mapper) package to interact smoothly with our database
- dgrijalva/jwt-go — Used to sign and verify JWT tokens
- joho/godotenv — Used to load .env files into the project
If you don't know how to install those things please refer the previous article.
JWT Authentication
First of all lets implement the user authentication functionality. So basically the file name is auth.go which is located in the app directory.
To start give the package name and import the required libraries. The package name will be the directory name.
package app
import (
"../models"
u "../utils"
"context"
"fmt"
jwt "github.com/dgrijalva/jwt-go"
"net/http"
"os"
"strings"
)
There will be a error on "../models" importation as there is no any directory representing that directory. Let's ignore it for a moment and continue implementing as in the next article we will discuss about the models package.
var JwtAuthentication = func(next http.Handler) http.Handler {}
This is the method and we are going to use the function as a variable. I'll tell the reason later when it's come to practical scenario. Let me explain the function deceleration. So you can see the function declaration is common as other languages function declaration. The first parameters are used to take data into the function. We called it as method arguments. The next parameters are the return type variable(s). As you refer the go documentation this will make you easy to understand otherwise it will not. So if you haven't read the documentation start reading it before doing the tutorial.
Now inside the method body,
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
notAuth := []string{"/api/user/new", "/api/user/login"} //List of endpoints that doesn't require auth
requestPath := r.URL.Path //current request path
//check if request does not need authentication, serve the request if it doesn't need it
for _, value := range notAuth {
if value == requestPath {
next.ServeHTTP(w, r)
return
}
}
response := make(map[string] interface{})
tokenHeader := r.Header.Get("Authorization") //Grab the token from the header
if tokenHeader == "" { //Token is missing, returns with error code 403 Unauthorized
response = u.Message(false, "Missing auth token")
w.WriteHeader(http.StatusForbidden)
w.Header().Add("Content-Type", "application/json")
u.Respond(w, response)
return
}
splitted := strings.Split(tokenHeader, " ") //The token normally comes in format `Bearer {token-body}`, we check if the retrieved token matched this requirement
if len(splitted) != 2 {
response = u.Message(false, "Invalid/Malformed auth token")
w.WriteHeader(http.StatusForbidden)
w.Header().Add("Content-Type", "application/json")
u.Respond(w, response)
return
}
tokenPart := splitted[1] //Grab the token part, what we are truly interested in
tk := &models.Token{}
token, err := jwt.ParseWithClaims(tokenPart, tk, func(token *jwt.Token) (interface{}, error) {
return []byte(os.Getenv("token_password")), nil
})
if err != nil { //Malformed token, returns with http code 403 as usual
response = u.Message(false, "Malformed authentication token")
w.WriteHeader(http.StatusForbidden)
w.Header().Add("Content-Type", "application/json")
u.Respond(w, response)
return
}
if !token.Valid { //Token is invalid, maybe not signed on this server
response = u.Message(false, "Token is not valid.")
w.WriteHeader(http.StatusForbidden)
w.Header().Add("Content-Type", "application/json")
u.Respond(w, response)
return
}
//Everything went well, proceed with the request and set the caller to the user retrieved from the parsed token
fmt.Sprintf("User %", tk.UserId) //Useful for monitoring
ctx := context.WithValue(r.Context(), "user", tk.UserId)
r = r.WithContext(ctx)
next.ServeHTTP(w, r) //proceed in the middleware chain!
});
So let's getting understand about the code.
notAuth := []string{"/api/user/new", "/api/user/login"} //List of endpoints that doesn't require auth
requestPath := r.URL.Path //current request path
//check if request does not need authentication, serve the request if it doesn't need it
for _, value := range notAuth {
if value == requestPath {
next.ServeHTTP(w, r)
return
}
}
So the above code will return a http response that the url's don't need the authentication. The no auth array holds the url's that we wan't to omit the authentication (jwt-token). And the requestPath variable will give the current url. The loop will do the rest.
response := make(map[string] interface{})
tokenHeader := r.Header.Get("Authorization") //Grab the token from the header
if tokenHeader == "" { //Token is missing, returns with error code 403 Unauthorized
response = u.Message(false, "Missing auth token")
w.WriteHeader(http.StatusForbidden)
w.Header().Add("Content-Type", "application/json")
u.Respond(w, response)
return
}
The above code is to identify whether there is a jwt-token in the request header or not. If there is no token value the response will be a message informing the url access is prohibited.
splitted := strings.Split(tokenHeader, " ") //The token normally comes in format `Bearer {token-body}`, we check if the retrieved token matched this requirement
if len(splitted) != 2 {
response = u.Message(false, "Invalid/Malformed auth token")
w.WriteHeader(http.StatusForbidden)
w.Header().Add("Content-Type", "application/json")
u.Respond(w, response)
return
}
In here this code is to check if the retrieved token matched with the right requirements. Do some self study on this method and try to understand the code.
tokenPart := splitted[1] //Grab the token part, what we are truly interested in
tk := &models.Token{}
token, err := jwt.ParseWithClaims(tokenPart, tk, func(token *jwt.Token) (interface{}, error) {
return []byte(os.Getenv("token_password")), nil
})
if err != nil { //Malformed token, returns with http code 403 as usual
response = u.Message(false, "Malformed authentication token")
w.WriteHeader(http.StatusForbidden)
w.Header().Add("Content-Type", "application/json")
u.Respond(w, response)
return
}
if !token.Valid { //Token is invalid, maybe not signed on this server
response = u.Message(false, "Token is not valid.")
w.WriteHeader(http.StatusForbidden)
w.Header().Add("Content-Type", "application/json")
u.Respond(w, response)
return
}
This code is to validate the jwt-token. whether it's signed in the right server or not, whether the token is malformed authentication token or not. Like wise the above method will return error if the token is invalid.
fmt.Sprintf("User %", tk.UserId) //Useful for monitoring
ctx := context.WithValue(r.Context(), "user", tk.UserId)
r = r.WithContext(ctx)
next.ServeHTTP(w, r) //proceed in the middleware chain!
So at last, if there is no error occurs and the token is valid, the instruction will return a http response informing the token is valid and the user can proceed to the next step.
So this article is to get some knowledge on jwt-token and jwt-authentication. Hope you get some knowledge in jwt token and jwt authentication and some basic go programming. Hope to see in coming up tutorials. Happy coding... ;)
No comments:
Post a Comment