Search code examples
gojwtauth0

Verify Auth0 ID Token - which secret do I need to use


I am using Auth0 to generate a JWT ID Token for the users. Now I would like to verify this ID Token in my go backend. I just want to extract the identity of the user from this key and check if it was created by my application in Auth0.

I am using the chi router with the jwtauth middleware. That is a lightweight wrapper around jwx.

The documentation says I should do the following:

    tokenAuth = jwtauth.New("RS256", []byte("secret"), nil)

I have tried several things as "secret", like my Auth0 Application's client secret or the Signing Certificate. But none of them seems to work. I double-checked if I am using the correct Signature Algorithm.

It always results in token is unauthorized.

    r.Route("/users", func(r chi.Router) {
        r.Use(jwtauth.Verifier(tokenAuth))
        //r.Use(jwtauth.Authenticator)
        r.Post("/info", usersService.InfoHandler)
    })

In the handler I try to get the token:

    token, _, err := jwtauth.FromContext(r.Context())
    fmt.Println("token", token)
    fmt.Println("error", err)

Solution

  • I have now created a middleware to do this. It's pretty straightforward forward and I think there is no need to use the jwtauth middleware.

    The best thing seems to use the JSON Web Key Set (JWKS). It contains all information to verify a JWT.

    package auth0
    
    import (
        "context"
        "fmt"
        "github.com/lestrrat-go/jwx/jwk"
        "github.com/lestrrat-go/jwx/jwt"
        log "github.com/sirupsen/logrus"
        "net/http"
    )
    
    var (
        ErrInvalidToken = fmt.Errorf("invalid token")
        ErrNoToken      = fmt.Errorf("no token found")
    )
    
    type middleware struct {
        keySet   jwk.Set
        audience string
        issuer   string
    }
    
    type AuthUser struct {
        ID    string `json:"id"`
        Email string `json:"email"`
    }
    
    type userKeyType string
    
    const userKey = userKeyType("user")
    
    type Middleware interface {
        AuthenticateUser(next http.Handler) http.Handler
    }
    
    var _ Middleware = &middleware{}
    
    func NewMiddleware(issuer string, audience string) (middleware, error) {
        // TODO implement auto rotation/refresh
        keySet, err := jwk.Fetch(context.Background(), fmt.Sprintf("%s.well-known/jwks.json", issuer))
        if err != nil {
            return middleware{}, err
        }
    
        return middleware{
            keySet:   keySet,
            audience: audience,
            issuer:   issuer,
        }, nil
    
    }
    
    func (m *middleware) AuthenticateUser(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    
            authHeader := r.Header.Get("Authorization")
            if authHeader == "" {
                log.Debug("no authorization header found")
                http.Error(w, ErrNoToken.Error(), http.StatusForbidden)
                return
            }
            bearerToken := authHeader[7:]
            if bearerToken == "" {
                log.Error("no bearer token found")
                http.Error(w, ErrNoToken.Error(), http.StatusForbidden)
                return
            }
    
            token, err := jwt.Parse([]byte(bearerToken), jwt.WithKeySet(m.keySet))
            if err != nil {
                log.Error("error parsing token")
                http.Error(w, ErrInvalidToken.Error(), http.StatusForbidden)
                return
            }
    
            if err := jwt.Validate(token,
                jwt.WithAudience(m.audience),
                jwt.WithIssuer(m.issuer)); err != nil {
                log.Error("error validating token")
                http.Error(w, ErrInvalidToken.Error(), http.StatusForbidden)
                return
            }
    
            emailValue, ok := token.Get("email")
            if !ok {
                log.Error("error no email found")
                http.Error(w, ErrInvalidToken.Error(), http.StatusForbidden)
                return
            }
    
            email, ok := emailValue.(string)
            if !ok {
                log.Error("error email not a string")
                http.Error(w, ErrInvalidToken.Error(), http.StatusForbidden)
                return
            }
    
            if token.Subject() == "" && email == "" {
                log.Error("error no subject or email found")
                http.Error(w, ErrInvalidToken.Error(), http.StatusForbidden)
                return
            }
    
            ctx := context.WithValue(r.Context(), userKey, AuthUser{
                ID:    token.Subject(),
                Email: email,
            })
    
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
    
    func GetUserFromContext(ctx context.Context) (AuthUser, error) {
        user, ok := ctx.Value(userKey).(AuthUser)
        if !ok {
            return AuthUser{}, fmt.Errorf("could not get user from context")
        }
        return user, nil
    }