Try it yourself with our free Jwt Decoder tool — runs entirely in your browser, no signup needed.

How to Decode JWT tokens in Go

How to Decode JWT Tokens in Go

JSON Web Tokens (JWTs) are a widely-used standard for securely transmitting information between parties. As a Go developer, being able to decode JWTs is a crucial skill for building secure and scalable applications. In this article, we'll walk through the process of decoding JWTs in Go, covering the basics, common edge cases, and performance tips.

Quick Example

Here's a minimal example that decodes a JWT token using the popular github.com/golang-jwt/jwt/v4 library:

package main

import (
	"fmt"
	"github.com/golang-jwt/jwt/v4"
)

func main() {
	token := "your_jwt_token_here"
	claims := jwt.MapClaims{}
	_, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
		return []byte("your_secret_key_here"), nil
	})
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(claims)
}

This code assumes you have the github.com/golang-jwt/jwt/v4 library installed. You can install it using the following command:

go get github.com/golang-jwt/jwt/v4

Step-by-Step Breakdown

Let's break down the code step by step:

  • We import the required libraries, including github.com/golang-jwt/jwt/v4.
  • We define a main function where we'll perform the JWT decoding.
  • We define a token variable containing the JWT token we want to decode.
  • We create a new jwt.MapClaims object to store the decoded claims.
  • We use the jwt.ParseWithClaims function to parse the JWT token. This function takes three arguments: the token, the claims, and a callback function that returns the secret key.
  • In the callback function, we return the secret key as a byte slice.
  • We check if there's an error during parsing. If there is, we print the error and return.
  • If parsing is successful, we print the decoded claims.

Handling Edge Cases

Empty/Null Input

When dealing with empty or null input, we should return an error to indicate that the input is invalid. We can do this by adding a simple check at the beginning of our function:

if token == "" {
    return errors.New("token is empty")
}

Invalid Input

If the input token is invalid (e.g., it's not a valid JWT token), jwt.ParseWithClaims will return an error. We can handle this by checking the error type:

if err != nil {
    if ve, ok := err.(*jwt.ValidationError); ok {
        if ve.Errors&jwt.ValidationErrorMalformed != 0 {
            return errors.New("token is malformed")
        } else if ve.Errors&jwt.ValidationErrorUnverifiable != 0 {
            return errors.New("token is unverifiable")
        }
    }
    return err
}

Large Input

When dealing with large input tokens, we should be mindful of performance. One way to optimize performance is to use a streaming parser instead of loading the entire token into memory. Unfortunately, github.com/golang-jwt/jwt/v4 doesn't support streaming parsing out of the box. However, we can use a third-party library like github.com/square/go-jose/v3 which supports streaming parsing.

Unicode/Special Characters

JWT tokens can contain Unicode characters, which can cause issues if not handled properly. Fortunately, github.com/golang-jwt/jwt/v4 handles Unicode characters correctly out of the box. However, if you're using a custom parser, make sure to handle Unicode characters correctly.

Common Mistakes

Mistake 1: Not Handling Errors

One common mistake is not handling errors properly. Make sure to check for errors after calling jwt.ParseWithClaims:

// Wrong
token, _ := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
    return []byte("your_secret_key_here"), nil
})

// Correct
token, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
    return []byte("your_secret_key_here"), nil
})
if err != nil {
    // Handle error
}

Mistake 2: Not Validating Claims

Another common mistake is not validating the decoded claims. Make sure to check the claims after parsing the token:

// Wrong
claims := jwt.MapClaims{}
token, _ := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
    return []byte("your_secret_key_here"), nil
})

// Correct
claims := jwt.MapClaims{}
token, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
    return []byte("your_secret_key_here"), nil
})
if err != nil {
    // Handle error
}
if claims["iss"] != "your_expected_issuer" {
    // Handle invalid claims
}

Mistake 3: Not Using a Secure Secret Key

Finally, make sure to use a secure secret key when parsing JWT tokens. Avoid hardcoding the secret key in your code:

// Wrong
func(token *jwt.Token) (interface{}, error) {
    return []byte("your_secret_key_here"), nil
}

// Correct
func(token *jwt.Token) (interface{}, error) {
    return []byte(os.Getenv("JWT_SECRET_KEY")), nil
}

Performance Tips

Tip 1: Use a Fast Parser

Use a fast JWT parser like github.com/golang-jwt/jwt/v4 which is optimized for performance.

Tip 2: Avoid Unnecessary Parsing

Avoid parsing the JWT token unnecessarily. If you only need to verify the token's signature, use the jwt.Parse function instead of jwt.ParseWithClaims.

Tip 3: Use a Cache

Use a cache to store the parsed JWT tokens. This can help reduce the number of parsing operations and improve performance.

FAQ

Q: What's the difference between jwt.Parse and jwt.ParseWithClaims?

jwt.Parse only verifies the token's signature, while jwt.ParseWithClaims also parses the claims.

Q: How do I handle large input tokens?

Use a streaming parser like github.com/square/go-jose/v3 to handle large input tokens.

Q: What's the best way to store the secret key?

Store the secret key securely using environment variables or a secure key store.

Q: How do I handle Unicode characters in JWT tokens?

Use a library like github.com/golang-jwt/jwt/v4 which handles Unicode characters correctly.

Q: What's the best way to validate decoded claims?

Validate the decoded claims by checking the expected values and handling any errors.

AI agent tools available. The CodeTidy MCP Server gives Claude, Cursor, and other AI agents access to 60+ developer tools. One command: npx @codetidy/mcp