When working with JWTs in Go, you’ll often need to extract and validate claims from the payload section. Let’s walk through how to do this effectively using the popular github.com/golang-jwt/jwt
package.
Basic JWT Structure
First, let’s understand what we’re parsing. A JWT consists of three parts:
- Header (algorithm and token type)
- Payload (claims)
- Signature
Setting Up Your Project
Start by installing the required package:
go get -u github.com/golang-jwt/jwt/v5
Creating a Claims Structure
Let’s define a structure to hold our claims:
type CustomClaims struct {
Issuer string `json:"iss"`
Subject string `json:"sub"`
ExpiresAt int64 `json:"exp"`
NotBefore int64 `json:"nbf"`
IssuedAt int64 `json:"iat"`
jwt.RegisteredClaims
}
Parsing the Token
Here’s a complete example showing how to parse a JWT and extract its claims:
package main
import (
"fmt"
"log"
"time"
"github.com/golang-jwt/jwt/v5"
)
func parseToken(tokenString string) (*CustomClaims, error) {
// Parse the token
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
// Replace this with your actual secret key
return []byte("your-secret-key"), nil
})
if err != nil {
return nil, fmt.Errorf("error parsing token: %v", err)
}
// Type assert the claims
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
return claims, nil
}
return nil, fmt.Errorf("invalid token claims")
}
func main() {
// Example JWT string - replace with your actual token
tokenString := "your.jwt.token"
claims, err := parseToken(tokenString)
if err != nil {
log.Fatalf("Error: %v", err)
}
// Access the claims
fmt.Printf("Issuer: %s\n", claims.Issuer)
fmt.Printf("Subject: %s\n", claims.Subject)
fmt.Printf("Expires At: %v\n", time.Unix(claims.ExpiresAt, 0))
fmt.Printf("Not Before: %v\n", time.Unix(claims.NotBefore, 0))
fmt.Printf("Issued At: %v\n", time.Unix(claims.IssuedAt, 0))
}
Validation
The JWT library automatically validates:
- Token structure
- Signature
- Expiration time (exp)
- Not Before time (nbf)
However, you might want to add custom validation:
func validateClaims(claims *CustomClaims) error {
// Example: Validate issuer
if claims.Issuer != "spike-nexus" {
return fmt.Errorf("invalid issuer")
}
// Example: Validate subject
if claims.Subject != "spike-admin" {
return fmt.Errorf("invalid subject")
}
return nil
}
Error Handling
Here’s how to handle common JWT errors:
func handleJWTError(err error) string {
switch {
case errors.Is(err, jwt.ErrTokenMalformed):
return "Token is malformed"
case errors.Is(err, jwt.ErrTokenExpired):
return "Token has expired"
case errors.Is(err, jwt.ErrTokenNotValidYet):
return "Token is not valid yet"
default:
return "Invalid token"
}
}
Best Practices
- Always validate the token signature using a secure key
- Check expiration and not-before claims
- Verify the issuer and subject claims match expected values
- Use proper error handling
- Consider using environment variables for sensitive values like secret keys
Testing
Here’s a simple test case:
func TestTokenParsing(t *testing.T) {
tokenString := "your.test.token"
claims, err := parseToken(tokenString)
if err != nil {
t.Errorf("Failed to parse token: %v", err)
}
if claims.Issuer != "spike-nexus" {
t.Errorf("Expected issuer 'spike-nexus', got '%s'", claims.Issuer)
}
}
Conclusion
Parsing JWT claims in Go is straightforward with the jwt-go
package. The key is to:
- Define a proper claims structure
- Use appropriate parsing methods
- Implement proper validation
- Handle errors gracefully
Remember to always validate tokens server-side and never trust client-provided tokens without verification.