Golang : How to login and logout with JWT example
I am now in the process of building a web application to help myself in managing my tenants in a better way. A common feature of a web application is to allow user to login and logout. Previously, I've written a tutorial on how to do this using Gorilla's session and for this tutorial I'll cover on how to program login and logout features in a web application using JSON Web Token(JWT).
There are couple of articles on why JWT is a better choice comparing to session that you can read up.
Below is an example that demonstrates how to use JSON Web Token and net/http.Cookie
to login a user, redirect to dashboard upon successful authentication and logout a user.
Here you go!
package main
import (
"fmt"
jwt "github.com/dgrijalva/jwt-go"
"golang.org/x/crypto/bcrypt"
"html/template"
"log"
"net/http"
"time"
)
// used to encrypt/decrypt JWT tokens. Change it to yours.
var jwtTokenSecret = "abc123456def"
const dashBoardPage = `<html><body>
{{if .Username}}
<p><b>{{.Username}}</b>, welcome to your dashboard! <a href="/logout">Logout!</a></p>
{{else}}
<p>Either your JSON Web token has expired or you've logged out! <a href="/login">Login</a></p>
{{end}}
</body></html>`
const logUserPage = `<html><body>
{{if .LoginError}}<p style="color:red">Either username or password is not in our record! Sign Up?</p>{{end}}
<form method="post" action="/login">
{{if .Username}}
<p><b>{{.Username}}</b>, you're already logged in! <a href="/logout">Logout!</a></p>
{{else}}
<label>Username:</label>
<input type="text" name="Username"><br>
<label>Password:</label>
<input type="password" name="Password">
<span style="font-style:italic"> Enter: mynakedpassword</span><br>
<input type="submit" name="Login" value="Let me in!">
{{end}}
</form>
</body></html>`
func DashBoardPageHandler(w http.ResponseWriter, r *http.Request) {
conditionsMap := map[string]interface{}{}
// THIS PAGE should ONLY obe accessible to those users that logged in
// check if user already logged in
username, _ := ExtractTokenUsername(r)
if username != "" {
conditionsMap["Username"] = username
}
if err := dashboardTemplate.Execute(w, conditionsMap); err != nil {
log.Println(err)
}
}
func LoginPageHandler(w http.ResponseWriter, r *http.Request) {
conditionsMap := map[string]interface{}{}
// check if user already logged in
username, _ := ExtractTokenUsername(r)
if username != "" { // user already logged in!
conditionsMap["Username"] = username
conditionsMap["LoginError"] = false
}
// verify username and password
if r.FormValue("Login") != "" && r.FormValue("Username") != "" {
username := r.FormValue("Username")
password := r.FormValue("Password")
// NOTE: here is where you want to query your database to retrieve the hashed password
// for username.
// For this tutorial and simplicity sake, we will simulate the retrieved hashed password
// as $2a$10$4Yhs5bfGgp4vz7j6ScujKuhpRTA4l4OWg7oSukRbyRN7dc.C1pamu
// the plain password is 'mynakedpassword'
// see https://www.socketloop.com/tutorials/golang-bcrypting-password for more details
// on how to generate bcrypted password
hashedPasswordFromDatabase := []byte("$2a$10$4Yhs5bfGgp4vz7j6ScujKuhpRTA4l4OWg7oSukRbyRN7dc.C1pamu")
if err := bcrypt.CompareHashAndPassword(hashedPasswordFromDatabase, []byte(password)); err != nil {
log.Println("Either username or password is wrong")
conditionsMap["LoginError"] = true
} else {
log.Println("Logged in :", username)
conditionsMap["Username"] = username
conditionsMap["LoginError"] = false
// create a new JSON Web Token and redirect to dashboard
tokenString, err := createToken(username)
if err != nil {
log.Println(err) // of course, this is too simple, your program should prevent login if token cannot be generated!!
os.Exit(1)
}
// create the cookie for client(browser)
expirationTime := time.Now().Add(1 * time.Hour) // cookie expired after 1 hour
cookie := &http.Cookie{
Name: "token",
Value: tokenString,
Expires: expirationTime,
}
http.SetCookie(w, cookie)
// After the cookie is created, the client(browser) will send in the cookie
// for every request. Our server side program will unpack the tokenString inside the cookie's Value
// for authentication before serving...
// This is one big advantage of JWT over session. The burden has been shifted to client instead of taking memory space
// on the server side. This helps a lot with the scaling process.
http.Redirect(w, r, "/dashboard", http.StatusFound)
}
}
if err := logUserTemplate.Execute(w, conditionsMap); err != nil {
log.Println(err)
}
}
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
// For JWT, log out is easy. Just destroy the cookie
// see https://golang.org/pkg/net/http/#Cookie
// Setting MaxAge<0 means delete cookie now.
c := http.Cookie{
Name: "token",
MaxAge: -1}
http.SetCookie(w, &c)
w.Write([]byte("Old cookie deleted. Logged out!\n"))
}
func createToken(username string) (string, error) {
claims := jwt.MapClaims{}
claims["authorized"] = true
claims["username"] = username //embed username inside the token string
claims["expired"] = time.Now().Add(time.Hour * 1).Unix() //Token expires after 1 hour
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(jwtTokenSecret))
}
func ExtractTokenUsername(r *http.Request) (string, error) {
// get our token string from Cookie
biscuit, err := r.Cookie("token")
var tokenString string
if err != nil {
tokenString = ""
} else {
tokenString = biscuit.Value
}
// abort
if tokenString == "" {
return "", nil
}
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return []byte(jwtTokenSecret), nil
})
if err != nil {
return "", err
}
claims, ok := token.Claims.(jwt.MapClaims)
if ok && token.Valid {
username := fmt.Sprintf("%s", claims["username"]) // convert to string
if err != nil {
return "", err
}
return username, nil
}
return "", nil
}
var dashboardTemplate = template.Must(template.New("").Parse(dashBoardPage))
var logUserTemplate = template.Must(template.New("").Parse(logUserPage))
func main() {
fmt.Println("Server starting, point your browser to localhost:8080/login to start")
http.HandleFunc("/login", LoginPageHandler)
http.HandleFunc("/dashboard", DashBoardPageHandler)
http.HandleFunc("/logout", LogoutHandler)
http.ListenAndServe(":8080", nil)
}
Run this program, login and play around by visiting /login
again after you've been redirected to /dashboard
.
Happy coding!
References :
See also : Golang : Login and logout a user after password verification and redirect example
By Adam Ng
IF you gain some knowledge or the information here solved your programming problem. Please consider donating to the less fortunate or some charities that you like. Apart from donation, planting trees, volunteering or reducing your carbon footprint will be great too.
Advertisement
Tutorials
+7.4k Golang : Mapping Iban to Dunging alphabets
+9.9k Golang : Check a web page existence with HEAD request example
+15k Golang : How to get Unix file descriptor for console and file
+9.8k Golang : Get escape characters \u form from unicode characters
+10.4k Golang : How to delete element(data) from map ?
+6.4k Golang : How to validate ISBN?
+21.8k Golang : Match strings by wildcard patterns with filepath.Match() function
+14.2k Golang : Parsing or breaking down URL
+9.3k Golang : Convert(cast) string to int64
+5.9k Golang : Get missing location after unmarshal binary and gob decode time.
+10.3k Golang : How to unmarshal JSON inner/nested value and assign to specific struct?
+10.2k Golang : Generate random integer or float number