https://ToniNichev@github.com/ToniNichev/tutorials-golang-authanticate-with-jwt.git
Generating JWT for testing
Setting up the project
we are going to use Gin Web framework to create simple HTTP server that we could query against, passing JWT in the header and then using secret or public key to validate the signature.
package main import ( "github.com/gin-gonic/gin" ) func AuthMiddleware() gin.HandlerFunc { // In a real-world application, you would perform proper authentication here. // For the sake of this example, we'll just check if an API key is present. return func(c *gin.Context) { apiKey := c.GetHeader("X-Auth-Token") if apiKey == "" { c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"}) return } c.Next() } } func main() { // Create a new Gin router router := gin.Default() // Public routes (no authentication required) public := router.Group("/public") { public.GET("/info", func(c *gin.Context) { c.String(200, "Public information") }) public.GET("/products", func(c *gin.Context) { c.String(200, "Public product list") }) } // Private routes (require authentication) private := router.Group("/private") private.Use(AuthMiddleware()) { private.GET("/data", func(c *gin.Context) { c.String(200, "Private data accessible after authentication") }) private.POST("/create", func(c *gin.Context) { c.String(200, "Create a new resource") }) } router.POST("query", AuthMiddleware(), validateSession, returnData) // Run the server on port 8080 router.Run(":8080") }
We added validateSession
middleware that will decode the token and verify the signature.
Creating JWT services to decode and validate signature
We are using jwt
GoLang library to decode the token, and validate the signature.
There are two ways to encode JWT: using symmetric encryption (meaning that the same secret is used to sign and validate the signature. This is done in retreiveTokenWithSymmetrikKey
and retreiveTokenWithAsymmetrikKey
is used to validate signature using the public key from the private/public key pair used to sign the token.
package main import ( "bytes" "encoding/json" "errors" "fmt" "io/ioutil" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" ) type User struct { name string email string } var user User type requestBody struct { OperationName *string `json:"operationName"` Query *string `json:"query"` Variables interface{} `json:"variables"` } func validateSession(c *gin.Context) { user.name = "" user.email = "" if c.Request.Body != nil { bodyBytes, _ := ioutil.ReadAll(c.Request.Body) c.Request.Body.Close() c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) body := requestBody{} if err := json.Unmarshal(bodyBytes, &body); err != nil { return } // extract the token from the headers tokenStr := c.Request.Header.Get("X-Auth-Token") product := body.Variables.(map[string]interface{})["product"] var payload string var err error if product == "web" { payload, err = retreiveTokenWithSymmetrikKey(c, tokenStr) } else { payload, err = retreiveTokenWithAsymmetrikKey(c, tokenStr) } if err != nil { c.AbortWithStatusJSON(401, gin.H{"error": "Session token signature can't be confirmed!"}) } if payload == "" { c.AbortWithStatusJSON(401, gin.H{"error": "Invalid token"}) return } c.Next() } } func retreiveTokenWithSymmetrikKey(c *gin.Context, tokenStr string) (string, error) { fmt.Println("retreive Token With Symmetric Key ...") tknStr := c.Request.Header.Get("X-Auth-Token") secretKey := "itsasecret123" token, err := jwt.Parse(tknStr, func(token *jwt.Token) (interface{}, error) { return []byte(secretKey), nil }) if err != nil { c.AbortWithStatusJSON(401, gin.H{"error": "Session token signature can't be confirmed!"}) return "", errors.New("session token signature can't be confirmed!") } else { claims := token.Claims.(jwt.MapClaims) fmt.Println("======================================") fmt.Println(claims) fmt.Println(claims["author"]) fmt.Println(claims["data"]) fmt.Println("======================================") user.name = claims["author"].(string) } return "token valid", nil } func retreiveTokenWithAsymmetrikKey(c *gin.Context, tokenStr string) (string, error) { fmt.Println("retreive Token With Asymmetric Key ...") publicKeyPath := "key/public_key.pem" keyData, err := ioutil.ReadFile(publicKeyPath) if err != nil { c.AbortWithStatusJSON(401, gin.H{"error": "Error reading public key"}) return "", errors.New("error reading public key") } var parsedToken *jwt.Token // parse token state, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) { // ensure signing method is correct if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { c.AbortWithStatusJSON(401, gin.H{"error": "Session token signature can't be confirmed!"}) return nil, errors.New("unknown signing method") } parsedToken = token // verify key, err := jwt.ParseRSAPublicKeyFromPEM([]byte(keyData)) if err != nil { return nil, errors.New("parsing key failed") } return key, nil }) claims := state.Claims.(jwt.MapClaims) fmt.Println("======================================") fmt.Println("Header [alg]:", parsedToken.Header["alg"]) fmt.Println("Header [expiresIn]:", parsedToken.Header["expiresIn"]) fmt.Println("Claims [author]:", claims["author"]) fmt.Println("Claims [data]:", claims["data"]) fmt.Println("======================================") user.name = claims["author"].(string) if !state.Valid { return "", errors.New("verification failed") } if err != nil { return "", errors.New("unknown signing error") } return "token valid", nil } func returnData(c *gin.Context) { fmt.Println("Returning data ...") c.String(200, user.name) }
Making requests with JWT
We are going to use Postman to make a new POST request passing the JWT
Generate JWT
Open the second project: Sign, Verify and decode JWT
Make sure that you comment and uncomment the right type of token that you want to use: asymmetric vs symmetric.
Run the project yarn start
end copy the long string printed right after SIGNED JWT.
Create new postman POST request
Open Postman and create new POST request. In the url put http://localhost:8080/query
this is where our Gin Web server running.
Add X-Auth-Token JWT
Open header section, and add X-Auth-Token
key with the value the JWT copied from Sign, Verify and decode JWT
Add query parameters and variables.
We are going to pass dummy parameters just for testing except for product
We are going to use product parameter to distinguish between symmetric and asymmetric tokens.
Let’s assume that our app will except symmetric tokens for web
and asymmetric
for app
so make sure that you will pass the right JWT.
Navigate to the GraphQL section of the request, and add the query and the variables.
query
query GetCustomerReccomendations($customerId: String!, $organization: organization!, $product: String!) { getCustomer(customerId: $customerId) { customerId zipCode } } }
variables
{ "customerId": "2b59f049-04d1-43d5-ac87-8ac62069d932", "organization": "nbcnews", "product": "app" }
Make the request and check the response
If everything works good, you will see the user name printed in the response.