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:

  1. Header (algorithm and token type)
  2. Payload (claims)
  3. 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

  1. Always validate the token signature using a secure key
  2. Check expiration and not-before claims
  3. Verify the issuer and subject claims match expected values
  4. Use proper error handling
  5. 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.