Introduction

When working with JSON Web Tokens (JWTs) in Go, especially those issued by Okta, validating the signature can be a bit challenging due to the use of JSON Web Keys (JWKs). This article will walk you through the process of decoding and validating a JWT's signature using Go.

Understanding JWT and JWK

JWTs are compact, URL-safe tokens that represent claims to be transferred between two parties. Okta uses JWKs to sign these tokens, which means you need to retrieve the appropriate public key to verify the signature.

Steps to Validate a JWT Signature

  1. Retrieve the JWKs: You can obtain the JWKs from your Okta authorization server. The endpoint typically looks like this:

    https://{yourOktaDomain}/oauth2/v1/keys
  2. Decode the JWT: Use a JWT library in Go to decode the JWT. This will give you access to the header and payload.

  3. Verify the Signature: Using the JWK corresponding to the JWT's kid (Key ID), verify the signature of the JWT.

Example Code

Here’s a simple example demonstrating how to validate a JWT in Go:

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "github.com/dgrijalva/jwt-go"
)

// JWK structure to hold the keys
type JWK struct {
    Keys []struct {
        Alg string `json:"alg"`
        E   string `json:"e"`
        N   string `json:"n"`
        Kid string `json:"kid"`
        Kty string `json:"kty"`
        Use string `json:"use"`
    } `json:"keys"`
}

func main() {
    // Example JWT
    tokenString := "eyJhbGciOiJSUzI1NiIsImtpZCI6..."

    // Fetch JWKs from Okta
    resp, err := http.Get("https://{yourOktaDomain}/oauth2/v1/keys")
    if err != nil {
        fmt.Println("Error fetching JWKs:", err)
        return
    }
    defer resp.Body.Close()

    var jwk JWK
    json.NewDecoder(resp.Body).Decode(&jwk)

    // Decode the JWT
    token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
        // Validate the algorithm
        if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
            return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
        }

        // Find the key by kid
        kid := token.Header["kid"].(string)
        for _, key := range jwk.Keys {
            if key.Kid == kid {
                // Convert JWK to RSA public key
                return jwt.ParseRSAPublicKeyFromPEM([]byte(key.N))
            }
        }
        return nil, fmt.Errorf("key not found")
    })

    if err != nil {
        fmt.Println("Error validating token:", err)
        return
    }

    // Token is valid
    fmt.Println("Token is valid:", token)
}

Conclusion

Validating JWT signatures in Go using Okta's JWKs involves fetching the keys, decoding the JWT, and verifying the signature against the appropriate key. By following the steps outlined in this guide, you can ensure that your application securely validates JWTs issued by Okta.