jwtmiddleware.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. package jwtmiddleware
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "github.com/dgrijalva/jwt-go"
  7. "log"
  8. "net/http"
  9. "strings"
  10. )
  11. // A function called whenever an error is encountered
  12. type errorHandler func(w http.ResponseWriter, r *http.Request, err string)
  13. // TokenExtractor is a function that takes a request as input and returns
  14. // either a token or an error. An error should only be returned if an attempt
  15. // to specify a token was found, but the information was somehow incorrectly
  16. // formed. In the case where a token is simply not present, this should not
  17. // be treated as an error. An empty string should be returned in that case.
  18. type TokenExtractor func(r *http.Request) (string, error)
  19. // Options is a struct for specifying configuration options for the middleware.
  20. type Options struct {
  21. // The function that will return the Key to validate the JWT.
  22. // It can be either a shared secret or a public key.
  23. // Default value: nil
  24. ValidationKeyGetter jwt.Keyfunc
  25. // The name of the property in the request where the user information
  26. // from the JWT will be stored.
  27. // Default value: "user"
  28. UserProperty string
  29. // The function that will be called when there's an error validating the token
  30. // Default value:
  31. ErrorHandler errorHandler
  32. // A boolean indicating if the credentials are required or not
  33. // Default value: false
  34. CredentialsOptional bool
  35. // A function that extracts the token from the request
  36. // Default: FromAuthHeader (i.e., from Authorization header as bearer token)
  37. Extractor TokenExtractor
  38. // Debug flag turns on debugging output
  39. // Default: false
  40. Debug bool
  41. // When set, all requests with the OPTIONS method will use authentication
  42. // Default: false
  43. EnableAuthOnOptions bool
  44. // When set, the middelware verifies that tokens are signed with the specific signing algorithm
  45. // If the signing method is not constant the ValidationKeyGetter callback can be used to implement additional checks
  46. // Important to avoid security issues described here: https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/
  47. // Default: nil
  48. SigningMethod jwt.SigningMethod
  49. }
  50. type JWTMiddleware struct {
  51. Options Options
  52. }
  53. func OnError(w http.ResponseWriter, r *http.Request, err string) {
  54. http.Error(w, err, http.StatusUnauthorized)
  55. }
  56. // New constructs a new Secure instance with supplied options.
  57. func New(options ...Options) *JWTMiddleware {
  58. var opts Options
  59. if len(options) == 0 {
  60. opts = Options{}
  61. } else {
  62. opts = options[0]
  63. }
  64. if opts.UserProperty == "" {
  65. opts.UserProperty = "user"
  66. }
  67. if opts.ErrorHandler == nil {
  68. opts.ErrorHandler = OnError
  69. }
  70. if opts.Extractor == nil {
  71. opts.Extractor = FromAuthHeader
  72. }
  73. return &JWTMiddleware{
  74. Options: opts,
  75. }
  76. }
  77. func (m *JWTMiddleware) logf(format string, args ...interface{}) {
  78. if m.Options.Debug {
  79. log.Printf(format, args...)
  80. }
  81. }
  82. // Special implementation for Negroni, but could be used elsewhere.
  83. func (m *JWTMiddleware) HandlerWithNext(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
  84. err := m.CheckJWT(w, r)
  85. // If there was an error, do not call next.
  86. if err == nil && next != nil {
  87. next(w, r)
  88. }
  89. }
  90. func (m *JWTMiddleware) Handler(h http.Handler) http.Handler {
  91. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  92. // Let secure process the request. If it returns an error,
  93. // that indicates the request should not continue.
  94. err := m.CheckJWT(w, r)
  95. // If there was an error, do not continue.
  96. if err != nil {
  97. return
  98. }
  99. h.ServeHTTP(w, r)
  100. })
  101. }
  102. // FromAuthHeader is a "TokenExtractor" that takes a give request and extracts
  103. // the JWT token from the Authorization header.
  104. func FromAuthHeader(r *http.Request) (string, error) {
  105. authHeader := r.Header.Get("Authorization")
  106. if authHeader == "" {
  107. return "", nil // No error, just no token
  108. }
  109. // TODO: Make this a bit more robust, parsing-wise
  110. authHeaderParts := strings.Split(authHeader, " ")
  111. if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" {
  112. return "", errors.New("Authorization header format must be Bearer {token}")
  113. }
  114. return authHeaderParts[1], nil
  115. }
  116. // FromParameter returns a function that extracts the token from the specified
  117. // query string parameter
  118. func FromParameter(param string) TokenExtractor {
  119. return func(r *http.Request) (string, error) {
  120. return r.URL.Query().Get(param), nil
  121. }
  122. }
  123. // FromFirst returns a function that runs multiple token extractors and takes the
  124. // first token it finds
  125. func FromFirst(extractors ...TokenExtractor) TokenExtractor {
  126. return func(r *http.Request) (string, error) {
  127. for _, ex := range extractors {
  128. token, err := ex(r)
  129. if err != nil {
  130. return "", err
  131. }
  132. if token != "" {
  133. return token, nil
  134. }
  135. }
  136. return "", nil
  137. }
  138. }
  139. func (m *JWTMiddleware) CheckJWT(w http.ResponseWriter, r *http.Request) error {
  140. if !m.Options.EnableAuthOnOptions {
  141. if r.Method == "OPTIONS" {
  142. return nil
  143. }
  144. }
  145. // Use the specified token extractor to extract a token from the request
  146. token, err := m.Options.Extractor(r)
  147. // If debugging is turned on, log the outcome
  148. if err != nil {
  149. m.logf("Error extracting JWT: %v", err)
  150. } else {
  151. m.logf("Token extracted: %s", token)
  152. }
  153. // If an error occurs, call the error handler and return an error
  154. if err != nil {
  155. m.Options.ErrorHandler(w, r, err.Error())
  156. return fmt.Errorf("Error extracting token: %v", err)
  157. }
  158. // If the token is empty...
  159. if token == "" {
  160. // Check if it was required
  161. if m.Options.CredentialsOptional {
  162. m.logf(" No credentials found (CredentialsOptional=true)")
  163. // No error, just no token (and that is ok given that CredentialsOptional is true)
  164. return nil
  165. }
  166. // If we get here, the required token is missing
  167. errorMsg := "Required authorization token not found"
  168. m.Options.ErrorHandler(w, r, errorMsg)
  169. m.logf(" Error: No credentials found (CredentialsOptional=false)")
  170. return fmt.Errorf(errorMsg)
  171. }
  172. // Now parse the token
  173. parsedToken, err := jwt.Parse(token, m.Options.ValidationKeyGetter)
  174. // Check if there was an error in parsing...
  175. if err != nil {
  176. m.logf("Error parsing token: %v", err)
  177. m.Options.ErrorHandler(w, r, err.Error())
  178. return fmt.Errorf("Error parsing token: %v", err)
  179. }
  180. if m.Options.SigningMethod != nil && m.Options.SigningMethod.Alg() != parsedToken.Header["alg"] {
  181. message := fmt.Sprintf("Expected %s signing method but token specified %s",
  182. m.Options.SigningMethod.Alg(),
  183. parsedToken.Header["alg"])
  184. m.logf("Error validating token algorithm: %s", message)
  185. m.Options.ErrorHandler(w, r, errors.New(message).Error())
  186. return fmt.Errorf("Error validating token algorithm: %s", message)
  187. }
  188. // Check if the parsed token is valid...
  189. if !parsedToken.Valid {
  190. m.logf("Token is invalid")
  191. m.Options.ErrorHandler(w, r, "The token isn't valid")
  192. return errors.New("Token is invalid")
  193. }
  194. m.logf("JWT: %v", parsedToken)
  195. // If we get here, everything worked and we can set the
  196. // user property in context.
  197. newRequest := r.WithContext(context.WithValue(r.Context(), m.Options.UserProperty, parsedToken))
  198. // Update the current request with the new context information.
  199. *r = *newRequest
  200. return nil
  201. }