understanding jwt in golang with a simple app

u

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

(source: https://jwt.io/)

So basically whenever we want to use authentication, we can use this to attach data to the header.

Here are some scenarios where JSON Web Tokens are useful:

  • Authorization
  • Information Exchange

What is the JSON Web Token structure?

In its compact form, JSON Web Tokens consist of three parts separated by dots (.), which are:

  • Header
  • Payload
  • Signature

Therefore, a JWT typically looks like the following.

xxxxx.yyyyy.zzzzz

to be honest, we don’t care how its work. we’re just gonna use it. But if you want you can read more about it here: https://jwt.io/introduction/

write a basic web server

We already learn how to do it. So create a one with MUX

package main

import (
	"html/template"
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

func serveIndex(w http.ResponseWriter,r *http.Request) {

	t, _ := template.ParseFiles("index.html")
	t.Execute(w, nil)

}

func main() {
	// Init router
	r := mux.NewRouter()

	// Route handles & endpoints
	r.HandleFunc("/", serveIndex).Methods("GET")

	// Start server
	log.Fatal(http.ListenAndServe(":8080", r))

}

And create a index.html file

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Jwt</title>
</head>
<body>
  hi
</body>
</html>

when you run the code, it should work

Now lets write a new route

func getSecret(w http.ResponseWriter, r *http.Request) {

	json.NewEncoder(w).Encode("This is a secret text")

}
r.HandleFunc("/secret", getSecret).Methods("GET")

Lets hold on a bit. Lets write some JS.

Basically in your index.html create 2 buttons. generate jwt and getsecret and write listeners for it js

<div id="container">
	<button>get jwt</button>
	<p id="jwt-status"></p>
	<button>get secret</button>
</div>
<script>
		let status = document.getElementById('jwt-status')

		//lets write listeners for the buttons
		let getJwt = () => {
			console.log('get jwt')
		}
		let getSecret = () => {
			console.log('get secret')
		}
	</script>

Now whenever user clicks on get secret, use fetch to send a request to our golang server on /secret route

Here is the JS code for it

<script>
		let status = document.getElementById('jwt-status')

		//lets write listeners for the buttons
		let getJwt = () => {
			console.log('get jwt')
		}

		let getSecret = () => {
			console.log('get secret')

			fetch('http://localhost:8080/secret')
				.then(res => res.json())
				.then(data => {
					status.innerText = data
				})
				.catch(err => console.log(err))
		}
	</script>

Now when user click on the get secret button, the “This is a secret text” appears.

So here’s the idea, I want that text to appear iff I am authorized(i.e) I should have the JWT.

Lets create a jwt

First go get it.

go get github.com/dgrijalva/jwt-go

Now lets write a function to create jwt

  • use token := jwt.New(jwt.SigningMethodHS256)
  • create payload using token.Claims.(jwt.MapClaims)
  • return it

Heres the code:

func GenerateJWT() (string, error) {
    token := jwt.New(jwt.SigningMethodHS256)

    claims := token.Claims.(jwt.MapClaims)

    //add payload
    claims["authorized"] = true
    claims["client"] = "Vigneshwar"
    claims["exp"] = time.Now().Add(time.Minute * 60).Unix()

    //generate the token
    tokenString, err := token.SignedString(mySigningKey)

    if err != nil {
        fmt.Errorf("Something Went Wrong: %s", err.Error())
        return "", err
    }

    return tokenString, nil
}

Now lets send it.

r.HandleFunc("/get-jwt", getJWT).Methods("GET")
func getJWT(w http.ResponseWriter, r *http.Request){
	validToken, err := GenerateJWT()
	if err != nil {
			fmt.Println("Failed to generate token")
	}

	json.NewEncoder(w).Encode(validToken)

}

Great now lets change the html/js

<body>
		<div id="container">
			<button id="get-jwt" onclick="getJwt()">get jwt</button>
			<p id="jwt-status"></p>
			<button id="get-secret" onclick="getSecret()">get secret</button>
			<p id="secret-status"></p>
		</div>
	</body>
	<script>
		let jwtStatus = document.getElementById('jwt-status')
		let secretStatus = document.getElementById('secret-status')

		//lets write listeners for the buttons
		let getJwt = () => {
			console.log('get jwt')

			fetch('http://localhost:8080/get-jwt')
				.then(res => res.json())
				.then(data => {
					jwtStatus.innerText = data
				})
				.catch(err => console.log(err))
		}

		let getSecret = () => {
			console.log('get secret')

			fetch('http://localhost:8080/secret')
				.then(res => res.json())
				.then(data => {
					secretStatus.innerText = data
				})
				.catch(err => console.log(err))
		}
	</script>

Great! we successfully generated the token. Now whenever I try to click get secret, I should get “This is a secret text” iff I already got the token by clicking get jwt atleast once.

How to do that?

Authentication

r.HandleFunc("/secret", getSecret).Methods("GET")

This is the route that handles /secret. Now lets change it into

r.HandleFunc("/secret", isAuthorized(getSecret)).Methods("GET")

And write the isAuthorized function as

func isAuthorized(endpoint http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Println(r.Header)
		fmt.Println()
		if r.Header["Token"] != nil {
                   //parse it and check it
		} else {
			json.NewEncoder(w).Encode("not authorized")
		}
	})
}

Now we can say some stuffs like

if token.Valid {
	endpoint.ServeHTTP(w, r)
}

To call the secHandler Function.

Here’s the full code of isAuthorized


func isAuthorized(endpoint http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Println(r.Header)
		fmt.Println()
		if r.Header["Token"] != nil {

			token, err := jwt.Parse(r.Header["Token"][0], func(token *jwt.Token) (interface{}, error) {
				if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
					return nil, fmt.Errorf("There was an error")
				}
				return mySigningKey, nil
			})

			if err != nil {
				json.NewEncoder(w).Encode("error")
			}

			if token.Valid {
				endpoint.ServeHTTP(w, r)
			}
		} else {
			json.NewEncoder(w).Encode("not authorized")
		}
	})
}

And thats it we did it. Now before doing anything lets also change our html/css

<script>
		let jwtStatus = document.getElementById('jwt-status')
		let secretStatus = document.getElementById('secret-status')

		let token = null

		//lets write listeners for the buttons
		let getJwt = () => {
			console.log('get jwt')

			fetch('http://localhost:8080/get-jwt')
				.then(res => res.json())
				.then(data => {
					token = data
					jwtStatus.innerText = data
				})
				.catch(err => console.log(err))
		}

		let getSecret = () => {
			console.log('get secret')

			if (token) {
				fetch('http://localhost:8080/secret', {
					headers: {
						Token: token,
					},
				})
					.then(res => res.json())
					.then(data => {
						secretStatus.innerText = data
						return
					})
					.catch(err => {
						console.log(err)
						return
					})
			} else {
				fetch('http://localhost:8080/secret')
					.then(res => res.json())
					.then(data => {
						secretStatus.innerText = data
					})
					.catch(err => console.log(err))
			}
		}
	</script>

So I basically send the token as a header, if I already incle clicked the get jwt Button.

Now lets check it.

  1. click get secret

Also check the console

We didn’t see any Token header. Great.

2) Now lets first click get jwt and then get secret

And sure enough we got the token printed in console too

It worked! Now we know how to implement authentication using JWT.

Lemme just post the whole code for reference

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<meta http-equiv="X-UA-Compatible" content="ie=edge" />
		<title>Jwt</title>
	</head>
	<body>
		<div id="container">
			<button id="get-jwt" onclick="getJwt()">get jwt</button>
			<p id="jwt-status"></p>
			<button id="get-secret" onclick="getSecret()">get secret</button>
			<p id="secret-status"></p>
		</div>
	</body>
	<script>
		let jwtStatus = document.getElementById('jwt-status')
		let secretStatus = document.getElementById('secret-status')

		let token = null

		//lets write listeners for the buttons
		let getJwt = () => {
			console.log('get jwt')

			fetch('http://localhost:8080/get-jwt')
				.then(res => res.json())
				.then(data => {
					token = data
					jwtStatus.innerText = data
				})
				.catch(err => console.log(err))
		}

		let getSecret = () => {
			console.log('get secret')

			if (token) {
				fetch('http://localhost:8080/secret', {
					headers: {
						Token: token,
					},
				})
					.then(res => res.json())
					.then(data => {
						secretStatus.innerText = data
						return
					})
					.catch(err => {
						console.log(err)
						return
					})
			} else {
				fetch('http://localhost:8080/secret')
					.then(res => res.json())
					.then(data => {
						secretStatus.innerText = data
					})
					.catch(err => console.log(err))
			}
		}
	</script>
</html>
package main

import (
	"encoding/json"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"time"

	jwt "github.com/dgrijalva/jwt-go"
	"github.com/gorilla/mux"
)

//just write anything you want
var mySigningKey = []byte("iamtheboneofmysword")

func GenerateJWT() (string, error) {
	token := jwt.New(jwt.SigningMethodHS256)

	claims := token.Claims.(jwt.MapClaims)

	claims["authorized"] = true
	claims["client"] = "Vigneshwar"
	claims["exp"] = time.Now().Add(time.Minute * 60).Unix()

	tokenString, err := token.SignedString(mySigningKey)

	if err != nil {
		fmt.Errorf("Something Went Wrong: %s", err.Error())
		return "", err
	}

	return tokenString, nil
}

func serveIndex(w http.ResponseWriter, r *http.Request) {

	t, _ := template.ParseFiles("index.html")
	t.Execute(w, nil)

}

func getSecret(w http.ResponseWriter, r *http.Request) {

	json.NewEncoder(w).Encode("This is a secret text")

}

func getJWT(w http.ResponseWriter, r *http.Request) {
	validToken, err := GenerateJWT()
	if err != nil {
		fmt.Println("Failed to generate token")
	}

	json.NewEncoder(w).Encode(validToken)

}

func isAuthorized(endpoint http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Println(r.Header)
		fmt.Println()
		if r.Header["Token"] != nil {

			token, err := jwt.Parse(r.Header["Token"][0], func(token *jwt.Token) (interface{}, error) {
				if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
					return nil, fmt.Errorf("There was an error")
				}
				return mySigningKey, nil
			})

			if err != nil {
				json.NewEncoder(w).Encode("error")
			}

			if token.Valid {
				endpoint.ServeHTTP(w, r)
			}
		} else {
			json.NewEncoder(w).Encode("not authorized")
		}
	})
}

func main() {
	// Init router
	r := mux.NewRouter()
	secHandler := http.HandlerFunc(getSecret)

	// Route handles & endpoints
	r.HandleFunc("/", serveIndex).Methods("GET")
	r.Handle("/secret", isAuthorized(secHandler)).Methods("GET")
	r.HandleFunc("/get-jwt", getJWT).Methods("GET")

	// Start server
	log.Fatal(http.ListenAndServe(":8080", r))

}

Happy coding!

About the author

vigneshwar

1 comment

Leave a Reply

By vigneshwar

Most common tags

%d bloggers like this: