Alternate title: Working with JWT, CORS as middlewares using Negroni.
This is Part 3 of "Learn go" series. You can find the previous post here.
In this post, I will be walking you through an example of adding middlewares for working with JWT for sessions. As well as making an API, CORS compatible. This post picks up from the previous posts Part I and, Part II, so if you haven't read, please skim through it.
Let's begin...
We need an API where when we login, we get a JWT token back for authentication, similar in the manner to session cookies.
We are creating a login handler. Let's call it CreateSessionHandler
type credentials struct {
Username string
Password string
}
func CreateSessionHandler(w http.ResponseWriter, req *http.Request) {
var creds credentials
json.NewDecoder(req.Body).Decode(&creds)
var user model.User
db.Instance().Where("username = ?", creds.Username).First(&user)
if model.ComparePasswords(user.HashedPassword, creds.Password) {
tokenMap := model.CreateJWTToken(user, jwtSigningKey)
json.NewEncoder(w).Encode(tokenMap)
} else {
http.Error(w, "Not Authorized", unauthorized)
return
}
}
How does the ComparePasswords
and CreateJWTToken
actually work?
func ComparePasswords(hashedPwd string, plainPwd string) bool {
byteHash := []byte(hashedPwd)
err := bcrypt.CompareHashAndPassword(byteHash, []byte(plainPwd))
if err != nil {
log.Println(err)
return false
}
return true
}
For comparing passwords, we use bcrypt's CompareHashAndPassword
function.
func CreateJWTToken(user User, jwtSigningKey []byte) map[string]string {
token := jwt.New(jwt.SigningMethodHS256)
/* Create a map to store our claims */
claims := token.Claims.(jwt.MapClaims)
/* Set token claims */
claims["user"] = user
claims["userID"] = user.ID
/* Sign the token with our secret */
tokenString, _ := token.SignedString(jwtSigningKey)
tokenMap := map[string]string{"token": tokenString}
return tokenMap
}
For creating a jwtToken we need a jwtSigningKey
defined. This is a random alphanumeric string. This needs to be kept secret as this verifies the signed token's authenticity. We can then create a new token using jwt.
We aren't done yet. We have created the token, now we need a way to verify and validate the JWT token in requests for other APIs.
Let's first write the middleware...
jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{
ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
return jwtSigningKey, nil
},
SigningMethod: jwt.SigningMethodHS256,
})
Note: This is a third-party middleware.
We can hook this up in multiple ways to our mux router. Let me use a middleware library called negroni
. Rewriting the example from the Part I, as
router.HandleFunc("/hello", negroni.New(
negroni.HandlerFunc(jwtMiddleware.HandlerWithNext),
negroni.Wrap(helloHandler),
)).Methods("GET")
We are creating a new Negroni
instance and passing it the jwtMiddleware's HandlerWithNext
function and the wrapped helloHandler
function.
We are almost done. Our API is almost functional. We run it, test it and it works fine. Then we hit the API from the UI on a different domain, and BAM it doesn't work. Bummer!
Well, we forgot to enable CORS support to our server. Duh!
Let's do this by using a simple CORS package.
handler := cors.Default().Handler(router)
http.ListenAndServe(":"+8080, handler)
We are wrapping our router with the default cors' Handler
. We open up our browser and test again. And voila! Things work like a charm.
Signing off for now. As always, please leave your thoughts and comments in the section below.