// SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors //go:build go1.15 // +build go1.15 package middleware import ( "errors" "fmt" "net/http" "reflect" "github.com/golang-jwt/jwt" "github.com/labstack/echo/v4" ) // JWTConfig defines the config for JWT middleware. type JWTConfig struct { // Skipper defines a function to skip middleware. Skipper Skipper // BeforeFunc defines a function which is executed just before the middleware. BeforeFunc BeforeFunc // SuccessHandler defines a function which is executed for a valid token before middleware chain continues with next // middleware or handler. SuccessHandler JWTSuccessHandler // ErrorHandler defines a function which is executed for an invalid token. // It may be used to define a custom JWT error. ErrorHandler JWTErrorHandler // ErrorHandlerWithContext is almost identical to ErrorHandler, but it's passed the current context. ErrorHandlerWithContext JWTErrorHandlerWithContext // ContinueOnIgnoredError allows the next middleware/handler to be called when ErrorHandlerWithContext decides to // ignore the error (by returning `nil`). // This is useful when parts of your site/api allow public access and some authorized routes provide extra functionality. // In that case you can use ErrorHandlerWithContext to set a default public JWT token value in the request context // and continue. Some logic down the remaining execution chain needs to check that (public) token value then. ContinueOnIgnoredError bool // Signing key to validate token. // This is one of the three options to provide a token validation key. // The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey. // Required if neither user-defined KeyFunc nor SigningKeys is provided. SigningKey interface{} // Map of signing keys to validate token with kid field usage. // This is one of the three options to provide a token validation key. // The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey. // Required if neither user-defined KeyFunc nor SigningKey is provided. SigningKeys map[string]interface{} // Signing method used to check the token's signing algorithm. // Optional. Default value HS256. SigningMethod string // Context key to store user information from the token into context. // Optional. Default value "user". ContextKey string // Claims are extendable claims data defining token content. Used by default ParseTokenFunc implementation. // Not used if custom ParseTokenFunc is set. // Optional. Default value jwt.MapClaims Claims jwt.Claims // TokenLookup is a string in the form of ":" or ":,:" that is used // to extract token from the request. // Optional. Default value "header:Authorization". // Possible values: // - "header:" or "header::" // `` is argument value to cut/trim prefix of the extracted value. This is useful if header // value has static prefix like `Authorization: ` where part that we // want to cut is ` ` note the space at the end. // In case of JWT tokens `Authorization: Bearer ` prefix we cut is `Bearer `. // If prefix is left empty the whole value is returned. // - "query:" // - "param:" // - "cookie:" // - "form:" // Multiple sources example: // - "header:Authorization,cookie:myowncookie" TokenLookup string // TokenLookupFuncs defines a list of user-defined functions that extract JWT token from the given context. // This is one of the two options to provide a token extractor. // The order of precedence is user-defined TokenLookupFuncs, and TokenLookup. // You can also provide both if you want. TokenLookupFuncs []ValuesExtractor // AuthScheme to be used in the Authorization header. // Optional. Default value "Bearer". AuthScheme string // KeyFunc defines a user-defined function that supplies the public key for a token validation. // The function shall take care of verifying the signing algorithm and selecting the proper key. // A user-defined KeyFunc can be useful if tokens are issued by an external party. // Used by default ParseTokenFunc implementation. // // When a user-defined KeyFunc is provided, SigningKey, SigningKeys, and SigningMethod are ignored. // This is one of the three options to provide a token validation key. // The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey. // Required if neither SigningKeys nor SigningKey is provided. // Not used if custom ParseTokenFunc is set. // Default to an internal implementation verifying the signing algorithm and selecting the proper key. KeyFunc jwt.Keyfunc // ParseTokenFunc defines a user-defined function that parses token from given auth. Returns an error when token // parsing fails or parsed token is invalid. // Defaults to implementation using `github.com/golang-jwt/jwt` as JWT implementation library ParseTokenFunc func(auth string, c echo.Context) (interface{}, error) } // JWTSuccessHandler defines a function which is executed for a valid token. type JWTSuccessHandler func(c echo.Context) // JWTErrorHandler defines a function which is executed for an invalid token. type JWTErrorHandler func(err error) error // JWTErrorHandlerWithContext is almost identical to JWTErrorHandler, but it's passed the current context. type JWTErrorHandlerWithContext func(err error, c echo.Context) error // Algorithms const ( AlgorithmHS256 = "HS256" ) // ErrJWTMissing is error that is returned when no JWToken was extracted from the request. var ErrJWTMissing = echo.NewHTTPError(http.StatusBadRequest, "missing or malformed jwt") // ErrJWTInvalid is error that is returned when middleware could not parse JWT correctly. var ErrJWTInvalid = echo.NewHTTPError(http.StatusUnauthorized, "invalid or expired jwt") // DefaultJWTConfig is the default JWT auth middleware config. var DefaultJWTConfig = JWTConfig{ Skipper: DefaultSkipper, SigningMethod: AlgorithmHS256, ContextKey: "user", TokenLookup: "header:" + echo.HeaderAuthorization, TokenLookupFuncs: nil, AuthScheme: "Bearer", Claims: jwt.MapClaims{}, KeyFunc: nil, } // JWT returns a JSON Web Token (JWT) auth middleware. // // For valid token, it sets the user in context and calls next handler. // For invalid token, it returns "401 - Unauthorized" error. // For missing token, it returns "400 - Bad Request" error. // // See: https://jwt.io/introduction // See `JWTConfig.TokenLookup` // // Deprecated: Please use https://github.com/labstack/echo-jwt instead func JWT(key interface{}) echo.MiddlewareFunc { c := DefaultJWTConfig c.SigningKey = key return JWTWithConfig(c) } // JWTWithConfig returns a JWT auth middleware with config. // See: `JWT()`. // // Deprecated: Please use https://github.com/labstack/echo-jwt instead func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc { // Defaults if config.Skipper == nil { config.Skipper = DefaultJWTConfig.Skipper } if config.SigningKey == nil && len(config.SigningKeys) == 0 && config.KeyFunc == nil && config.ParseTokenFunc == nil { panic("echo: jwt middleware requires signing key") } if config.SigningMethod == "" { config.SigningMethod = DefaultJWTConfig.SigningMethod } if config.ContextKey == "" { config.ContextKey = DefaultJWTConfig.ContextKey } if config.Claims == nil { config.Claims = DefaultJWTConfig.Claims } if config.TokenLookup == "" && len(config.TokenLookupFuncs) == 0 { config.TokenLookup = DefaultJWTConfig.TokenLookup } if config.AuthScheme == "" { config.AuthScheme = DefaultJWTConfig.AuthScheme } if config.KeyFunc == nil { config.KeyFunc = config.defaultKeyFunc } if config.ParseTokenFunc == nil { config.ParseTokenFunc = config.defaultParseToken } extractors, cErr := createExtractors(config.TokenLookup, config.AuthScheme) if cErr != nil { panic(cErr) } if len(config.TokenLookupFuncs) > 0 { extractors = append(config.TokenLookupFuncs, extractors...) } return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { if config.Skipper(c) { return next(c) } if config.BeforeFunc != nil { config.BeforeFunc(c) } var lastExtractorErr error var lastTokenErr error for _, extractor := range extractors { auths, err := extractor(c) if err != nil { lastExtractorErr = ErrJWTMissing // backwards compatibility: all extraction errors are same (unlike KeyAuth) continue } for _, auth := range auths { token, err := config.ParseTokenFunc(auth, c) if err != nil { lastTokenErr = err continue } // Store user information from token into context. c.Set(config.ContextKey, token) if config.SuccessHandler != nil { config.SuccessHandler(c) } return next(c) } } // we are here only when we did not successfully extract or parse any of the tokens err := lastTokenErr if err == nil { // prioritize token errors over extracting errors err = lastExtractorErr } if config.ErrorHandler != nil { return config.ErrorHandler(err) } if config.ErrorHandlerWithContext != nil { tmpErr := config.ErrorHandlerWithContext(err, c) if config.ContinueOnIgnoredError && tmpErr == nil { return next(c) } return tmpErr } // backwards compatible errors codes if lastTokenErr != nil { return &echo.HTTPError{ Code: ErrJWTInvalid.Code, Message: ErrJWTInvalid.Message, Internal: err, } } return err // this is lastExtractorErr value } } } func (config *JWTConfig) defaultParseToken(auth string, c echo.Context) (interface{}, error) { var token *jwt.Token var err error // Issue #647, #656 if _, ok := config.Claims.(jwt.MapClaims); ok { token, err = jwt.Parse(auth, config.KeyFunc) } else { t := reflect.ValueOf(config.Claims).Type().Elem() claims := reflect.New(t).Interface().(jwt.Claims) token, err = jwt.ParseWithClaims(auth, claims, config.KeyFunc) } if err != nil { return nil, err } if !token.Valid { return nil, errors.New("invalid token") } return token, nil } // defaultKeyFunc returns a signing key of the given token. func (config *JWTConfig) defaultKeyFunc(t *jwt.Token) (interface{}, error) { // Check the signing method if t.Method.Alg() != config.SigningMethod { return nil, fmt.Errorf("unexpected jwt signing method=%v", t.Header["alg"]) } if len(config.SigningKeys) > 0 { if kid, ok := t.Header["kid"].(string); ok { if key, ok := config.SigningKeys[kid]; ok { return key, nil } } return nil, fmt.Errorf("unexpected jwt key id=%v", t.Header["kid"]) } return config.SigningKey, nil }