[
  {
    "path": ".github/workflows/ci.yml",
    "content": "on:\n  push:\n    branches: '**'\n    paths-ignore:\n      - 'docs/**'\n  pull_request:\n    branches: '**'\n    paths-ignore:\n      - 'docs/**'\n\nname: Test\njobs:\n  test:\n    env:\n      GOPATH: ${{ github.workspace }}\n\n    defaults:\n      run:\n        working-directory: ${{ env.GOPATH }}/src/github.com/${{ github.repository }}\n\n    strategy:\n      matrix:\n        go-version: [1.22.x, 1.23.x, 1.24.x]\n        os: [ubuntu-latest, macos-latest, windows-latest]\n\n    runs-on: ${{ matrix.os }}\n\n    steps:\n    - name: Install Go\n      uses: actions/setup-go@v4\n      with:\n        go-version: ${{ matrix.go-version }}\n    - name: Checkout code\n      uses: actions/checkout@v3\n      with:\n        path: ${{ env.GOPATH }}/src/github.com/${{ github.repository }}\n    - name: Test\n      run: |\n        go get -d -t ./...\n        go test -v ./...\n"
  },
  {
    "path": ".gitignore",
    "content": "vendor/\nGopkg.lock\n.idea/"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2015-Present https://github.com/go-chi authors\n\nMIT License\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "SHELL            = bash -o pipefail\nTEST_FLAGS       ?= -v -race\n\nall:\n\t@echo \"make <cmd>\"\n\t@echo \"\"\n\t@echo \"commands:\"\n\t@echo \"\"\n\t@echo \" + Development:\"\n\t@echo \"   - build\"\n\t@echo \"   - test\"\n\t@echo \"   - todo\"\n\t@echo \"   - clean\"\n\t@echo \"\"\n\t@echo \"\"\n\n\n##\n## Development\n##\nbuild:\n\tgo build ./...\n\nclean:\n\tgo clean -cache -testcache\n\ntest: test-clean\n\tGOGC=off go test $(TEST_FLAGS) -run=$(TEST) ./...\n\ntest-clean:\n\tGOGC=off go clean -testcache\n\nbench:\n\t@go test -timeout=25m -bench=.\n\ntodo:\n\t@git grep TODO -- './*' ':!./vendor/' ':!./Makefile' || :\n"
  },
  {
    "path": "README.md",
    "content": "# jwtauth - JWT authentication middleware for HTTP services\n\n[![GoDoc Widget]][godoc]\n\nThe `jwtauth` http middleware package provides a simple way to verify a JWT token\nfrom a http request and send the result down the request context (`context.Context`).\n\nPlease note, `jwtauth` works with any Go http router, but resides under the go-chi group\nfor maintenance and organization - its only 3rd party dependency is the underlying jwt library\n\"github.com/lestrrat-go/jwx\".\n\nIn a complete JWT-authentication flow, you'll first capture the token from a http\nrequest, decode it, verify it and then validate that its correctly signed and hasn't\nexpired - the `jwtauth.Verifier` middleware handler takes care of all of that. The\n`jwtauth.Verifier` will set the context values on keys `jwtauth.TokenCtxKey` and\n`jwtauth.ErrorCtxKey`.\n\nNext, it's up to an authentication handler to respond or continue processing after the\n`jwtauth.Verifier`. The `jwtauth.Authenticator` middleware responds with a 401 Unauthorized\nplain-text payload for all unverified tokens and passes the good ones through. You can\nalso copy the Authenticator and customize it to handle invalid tokens to better fit\nyour flow (ie. with a JSON error response body).\n\nBy default, the `Verifier` will search for a JWT token in a http request, in the order:\n\n1.  'Authorization: BEARER T' request header\n2.  'jwt' Cookie value\n\nThe first JWT string that is found as an authorization header\nor cookie header is then decoded by the `lestrrat-go/jwx` library and a jwt.Token\nobject is set on the request context. In the case of a signature decoding error\nthe Verifier will also set the error on the request context.\n\nThe Verifier always calls the next http handler in sequence, which can either\nbe the generic `jwtauth.Authenticator` middleware or your own custom handler\nwhich checks the request context jwt token and error to prepare a custom\nhttp response.\n\nNote: jwtauth supports custom verification sequences for finding a token\nfrom a request by using the `Verify` middleware instantiator directly. The default\n`Verifier` is instantiated by calling `Verify(ja, TokenFromHeader, TokenFromCookie)`.\n\n# Usage\n\nSee the full [example](https://github.com/go-chi/jwtauth/blob/master/_example/main.go).\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/jwtauth/v5\"\n)\n\nvar tokenAuth *jwtauth.JWTAuth\n\nfunc init() {\n\ttokenAuth = jwtauth.New(\"HS256\", []byte(\"secret\"), nil)\n\n\t// For debugging/example purposes, we generate and print\n\t// a sample jwt token with claims `user_id:123` here:\n\t_, tokenString, _ := tokenAuth.Encode(map[string]interface{}{\"user_id\": 123})\n\tfmt.Printf(\"DEBUG: a sample jwt is %s\\n\\n\", tokenString)\n}\n\nfunc main() {\n\taddr := \":3333\"\n\tfmt.Printf(\"Starting server on %v\\n\", addr)\n\thttp.ListenAndServe(addr, router())\n}\n\nfunc router() http.Handler {\n\tr := chi.NewRouter()\n\n\t// Protected routes\n\tr.Group(func(r chi.Router) {\n\t\t// Seek, verify and validate JWT tokens\n\t\tr.Use(jwtauth.Verifier(tokenAuth))\n\n\t\t// Handle valid / invalid tokens. In this example, we use\n\t\t// the provided authenticator middleware, but you can write your\n\t\t// own very easily, look at the Authenticator method in jwtauth.go\n\t\t// and tweak it, its not scary.\n\t\tr.Use(jwtauth.Authenticator(tokenAuth))\n\n\t\tr.Get(\"/admin\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\t_, claims, _ := jwtauth.FromContext(r.Context())\n\t\t\tw.Write([]byte(fmt.Sprintf(\"protected area. hi %v\", claims[\"user_id\"])))\n\t\t})\n\t})\n\n\t// Public routes\n\tr.Group(func(r chi.Router) {\n\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.Write([]byte(\"welcome anonymous\"))\n\t\t})\n\t})\n\n\treturn r\n}\n```\n\n# Util\n\nSee https://github.com/goware/jwtutil for utility to help you generate JWT tokens.\n\n`go install github.com/goware/jwtutil`\n\nUsage: `jwtutil -secret=secret -encode -claims='{\"user_id\":111}'`\n\nOutput: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMTF9._cLJn0xFS0Mdr_4L_8XF8-8tv7bHyOQJXyWaNsSqlEs`\n\n\n# LICENSE\n\n[MIT](/LICENSE)\n\n[godoc]: https://pkg.go.dev/github.com/go-chi/jwtauth/v5\n[godoc widget]: https://godoc.org/github.com/go-chi/jwtauth?status.svg\n"
  },
  {
    "path": "_example/main.go",
    "content": "//\n// jwtauth example\n//\n// Sample output:\n//\n// [peter@pak ~]$ curl -v http://localhost:3333/\n// *   Trying ::1...\n// * Connected to localhost (::1) port 3333 (#0)\n// > GET / HTTP/1.1\n// > Host: localhost:3333\n// > User-Agent: curl/7.49.1\n// > Accept: */*\n// >\n// < HTTP/1.1 200 OK\n// < Date: Tue, 13 Sep 2016 15:53:17 GMT\n// < Content-Length: 17\n// < Content-Type: text/plain; charset=utf-8\n// <\n// * Connection #0 to host localhost left intact\n// welcome anonymous%\n//\n//\n// [peter@pak ~]$ curl -v http://localhost:3333/admin\n// *   Trying ::1...\n// * Connected to localhost (::1) port 3333 (#0)\n// > GET /admin HTTP/1.1\n// > Host: localhost:3333\n// > User-Agent: curl/7.49.1\n// > Accept: */*\n// >\n// < HTTP/1.1 401 Unauthorized\n// < Content-Type: text/plain; charset=utf-8\n// < X-Content-Type-Options: nosniff\n// < Date: Tue, 13 Sep 2016 15:53:19 GMT\n// < Content-Length: 13\n// <\n// Unauthorized\n// * Connection #0 to host localhost left intact\n//\n//\n// [peter@pak ~]$ curl -H\"Authorization: BEARER eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjN9.PZLMJBT9OIVG2qgp9hQr685oVYFgRgWpcSPmNcw6y7M\" -v http://localhost:3333/admin\n// *   Trying ::1...\n// * Connected to localhost (::1) port 3333 (#0)\n// > GET /admin HTTP/1.1\n// > Host: localhost:3333\n// > User-Agent: curl/7.49.1\n// > Accept: */*\n// > Authorization: BEARER eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjN9.PZLMJBT9OIVG2qgp9hQr685oVYFgRgWpcSPmNcw6y7M\n// >\n// < HTTP/1.1 200 OK\n// < Date: Tue, 13 Sep 2016 15:54:26 GMT\n// < Content-Length: 22\n// < Content-Type: text/plain; charset=utf-8\n// <\n// * Connection #0 to host localhost left intact\n// protected area. hi 123%\n//\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/jwtauth/v5\"\n\t\"github.com/lestrrat-go/jwx/v3/jwt\"\n)\n\nvar tokenAuth *jwtauth.JWTAuth\n\nfunc init() {\n\ttokenAuth = jwtauth.New(\"HS256\", []byte(\"secret\"), nil, jwt.WithAcceptableSkew(30*time.Second))\n\n\t// For debugging/example purposes, we generate and print\n\t// a sample jwt token with claims `user_id:123` here:\n\t_, tokenString, _ := tokenAuth.Encode(map[string]interface{}{\"user_id\": 123})\n\tfmt.Printf(\"DEBUG: a sample jwt is %s\\n\\n\", tokenString)\n}\n\nfunc main() {\n\taddr := \":3333\"\n\tfmt.Printf(\"Starting server on %v\\n\", addr)\n\thttp.ListenAndServe(addr, router())\n}\n\nfunc router() http.Handler {\n\tr := chi.NewRouter()\n\n\t// Protected routes\n\tr.Group(func(r chi.Router) {\n\t\t// Seek, verify and validate JWT tokens\n\t\tr.Use(jwtauth.Verifier(tokenAuth))\n\n\t\t// Handle valid / invalid tokens. In this example, we use\n\t\t// the provided authenticator middleware, but you can write your\n\t\t// own very easily, look at the Authenticator method in jwtauth.go\n\t\t// and tweak it, its not scary.\n\t\tr.Use(jwtauth.Authenticator(tokenAuth))\n\n\t\tr.Get(\"/admin\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\t_, claims, _ := jwtauth.FromContext(r.Context())\n\t\t\tw.Write([]byte(fmt.Sprintf(\"protected area. hi %v\", claims[\"user_id\"])))\n\t\t})\n\t})\n\n\t// Public routes\n\tr.Group(func(r chi.Router) {\n\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.Write([]byte(\"welcome anonymous\"))\n\t\t})\n\t})\n\n\treturn r\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/go-chi/jwtauth/v5\n\ngo 1.23.0\n\ntoolchain go1.24.2\n\nrequire (\n\tgithub.com/go-chi/chi/v5 v5.2.1\n\tgithub.com/lestrrat-go/jwx/v3 v3.0.2\n)\n\nrequire (\n\tgithub.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect\n\tgithub.com/goccy/go-json v0.10.3 // indirect\n\tgithub.com/lestrrat-go/blackmagic v1.0.3 // indirect\n\tgithub.com/lestrrat-go/httpcc v1.0.1 // indirect\n\tgithub.com/lestrrat-go/httprc/v3 v3.0.0-beta2 // indirect\n\tgithub.com/lestrrat-go/option v1.0.1 // indirect\n\tgithub.com/segmentio/asm v1.2.0 // indirect\n\tgolang.org/x/crypto v0.38.0 // indirect\n\tgolang.org/x/sys v0.33.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=\ngithub.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=\ngithub.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=\ngithub.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=\ngithub.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=\ngithub.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=\ngithub.com/lestrrat-go/blackmagic v1.0.3 h1:94HXkVLxkZO9vJI/w2u1T0DAoprShFd13xtnSINtDWs=\ngithub.com/lestrrat-go/blackmagic v1.0.3/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=\ngithub.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=\ngithub.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=\ngithub.com/lestrrat-go/httprc/v3 v3.0.0-beta2 h1:SDxjGoH7qj0nBXVrcrxX8eD94wEnjR+EEuqqmeqQYlY=\ngithub.com/lestrrat-go/httprc/v3 v3.0.0-beta2/go.mod h1:Nwo81sMxE0DcvTB+rJyynNhv/DUu2yZErV7sscw9pHE=\ngithub.com/lestrrat-go/jwx/v3 v3.0.2 h1:N+XLjTJEzDZRP3S0SezclXFAfopwL+o5vaL+qg6rX1I=\ngithub.com/lestrrat-go/jwx/v3 v3.0.2/go.mod h1:qO9w1qkQH77a0r9OXNM33YQPnV/evetKYRg58h1rBNE=\ngithub.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=\ngithub.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=\ngithub.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=\ngithub.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngolang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=\ngolang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=\ngolang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=\ngolang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "jwtauth.go",
    "content": "package jwtauth\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/lestrrat-go/jwx/v3/jwa\"\n\t\"github.com/lestrrat-go/jwx/v3/jwt\"\n\t\"github.com/lestrrat-go/jwx/v3/transform\"\n)\n\ntype JWTAuth struct {\n\talg             jwa.SignatureAlgorithm\n\tsignKey         interface{} // private-key\n\tverifyKey       interface{} // public-key, only used by RSA and ECDSA algorithms\n\tverifier        jwt.ParseOption\n\tvalidateOptions []jwt.ValidateOption\n}\n\nvar (\n\tTokenCtxKey = &contextKey{\"Token\"}\n\tErrorCtxKey = &contextKey{\"Error\"}\n)\n\nvar (\n\tErrUnauthorized = errors.New(\"token is unauthorized\")\n\tErrExpired      = errors.New(\"token is expired\")\n\tErrNBFInvalid   = errors.New(\"token nbf validation failed\")\n\tErrIATInvalid   = errors.New(\"token iat validation failed\")\n\tErrNoTokenFound = errors.New(\"no token found\")\n\tErrAlgoInvalid  = errors.New(\"algorithm mismatch\")\n)\n\nfunc New(alg string, signKey interface{}, verifyKey interface{}, validateOptions ...jwt.ValidateOption) *JWTAuth {\n\tsigAlg, _ := jwa.LookupSignatureAlgorithm(alg)\n\tja := &JWTAuth{\n\t\talg:             sigAlg,\n\t\tsignKey:         signKey,\n\t\tverifyKey:       verifyKey,\n\t\tvalidateOptions: validateOptions,\n\t}\n\n\tif ja.verifyKey != nil {\n\t\tja.verifier = jwt.WithKey(ja.alg, ja.verifyKey)\n\t} else {\n\t\tja.verifier = jwt.WithKey(ja.alg, ja.signKey)\n\t}\n\n\treturn ja\n}\n\n// Verifier http middleware handler will verify a JWT string from a http request.\n//\n// Verifier will search for a JWT token in a http request, in the order:\n//  1. 'Authorization: BEARER T' request header\n//  2. Cookie 'jwt' value\n//\n// The first JWT string that is found as a query parameter, authorization header\n// or cookie header is then decoded by the `jwt-go` library and a *jwt.Token\n// object is set on the request context. In the case of a signature decoding error\n// the Verifier will also set the error on the request context.\n//\n// The Verifier always calls the next http handler in sequence, which can either\n// be the generic `jwtauth.Authenticator` middleware or your own custom handler\n// which checks the request context jwt token and error to prepare a custom\n// http response.\nfunc Verifier(ja *JWTAuth) func(http.Handler) http.Handler {\n\treturn Verify(ja, TokenFromHeader, TokenFromCookie)\n}\n\nfunc Verify(ja *JWTAuth, findTokenFns ...func(r *http.Request) string) func(http.Handler) http.Handler {\n\treturn func(next http.Handler) http.Handler {\n\t\thfn := func(w http.ResponseWriter, r *http.Request) {\n\t\t\tctx := r.Context()\n\t\t\ttoken, err := VerifyRequest(ja, r, findTokenFns...)\n\t\t\tctx = NewContext(ctx, token, err)\n\t\t\tnext.ServeHTTP(w, r.WithContext(ctx))\n\t\t}\n\t\treturn http.HandlerFunc(hfn)\n\t}\n}\n\nfunc VerifyRequest(ja *JWTAuth, r *http.Request, findTokenFns ...func(r *http.Request) string) (jwt.Token, error) {\n\tvar tokenString string\n\n\t// Extract token string from the request by calling token find functions in\n\t// the order they where provided. Further extraction stops if a function\n\t// returns a non-empty string.\n\tfor _, fn := range findTokenFns {\n\t\ttokenString = fn(r)\n\t\tif tokenString != \"\" {\n\t\t\tbreak\n\t\t}\n\t}\n\tif tokenString == \"\" {\n\t\treturn nil, ErrNoTokenFound\n\t}\n\n\treturn VerifyToken(ja, tokenString)\n}\n\nfunc VerifyToken(ja *JWTAuth, tokenString string) (jwt.Token, error) {\n\t// Decode & verify the token\n\ttoken, err := ja.Decode(tokenString)\n\tif err != nil {\n\t\treturn token, ErrorReason(err)\n\t}\n\n\tif token == nil {\n\t\treturn nil, ErrUnauthorized\n\t}\n\n\tif err := jwt.Validate(token, ja.validateOptions...); err != nil {\n\t\treturn token, ErrorReason(err)\n\t}\n\n\t// Valid!\n\treturn token, nil\n}\n\nfunc (ja *JWTAuth) Encode(claims map[string]interface{}) (t jwt.Token, tokenString string, err error) {\n\tt = jwt.New()\n\tfor k, v := range claims {\n\t\tif err := t.Set(k, v); err != nil {\n\t\t\treturn nil, \"\", err\n\t\t}\n\t}\n\tpayload, err := ja.sign(t)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\ttokenString = string(payload)\n\treturn\n}\n\nfunc (ja *JWTAuth) Decode(tokenString string) (jwt.Token, error) {\n\treturn ja.parse([]byte(tokenString))\n}\n\nfunc (ja *JWTAuth) ValidateOptions() []jwt.ValidateOption {\n\treturn ja.validateOptions\n}\n\nfunc (ja *JWTAuth) sign(token jwt.Token) ([]byte, error) {\n\treturn jwt.Sign(token, jwt.WithKey(ja.alg, ja.signKey))\n}\n\nfunc (ja *JWTAuth) parse(payload []byte) (jwt.Token, error) {\n\t// we disable validation here because we use jwt.Validate to validate tokens\n\treturn jwt.Parse(payload, ja.verifier, jwt.WithValidate(false))\n}\n\n// ErrorReason will normalize the error message from the underlining\n// jwt library\nfunc ErrorReason(err error) error {\n\tswitch {\n\tcase errors.Is(err, jwt.TokenExpiredError()), err == ErrExpired:\n\t\treturn ErrExpired\n\tcase errors.Is(err, jwt.InvalidIssuedAtError()), err == ErrIATInvalid:\n\t\treturn ErrIATInvalid\n\tcase errors.Is(err, jwt.TokenNotYetValidError()), err == ErrNBFInvalid:\n\t\treturn ErrNBFInvalid\n\tdefault:\n\t\treturn ErrUnauthorized\n\t}\n}\n\n// Authenticator is a default authentication middleware to enforce access from the\n// Verifier middleware request context values. The Authenticator sends a 401 Unauthorized\n// response for any unverified tokens and passes the good ones through. It's just fine\n// until you decide to write something similar and customize your client response.\nfunc Authenticator(ja *JWTAuth) func(http.Handler) http.Handler {\n\treturn func(next http.Handler) http.Handler {\n\t\thfn := func(w http.ResponseWriter, r *http.Request) {\n\t\t\ttoken, _, err := FromContext(r.Context())\n\n\t\t\tif err != nil {\n\t\t\t\thttp.Error(w, err.Error(), http.StatusUnauthorized)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif token == nil {\n\t\t\t\thttp.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Token is authenticated, pass it through\n\t\t\tnext.ServeHTTP(w, r)\n\t\t}\n\t\treturn http.HandlerFunc(hfn)\n\t}\n}\n\nfunc NewContext(ctx context.Context, t jwt.Token, err error) context.Context {\n\tctx = context.WithValue(ctx, TokenCtxKey, t)\n\tctx = context.WithValue(ctx, ErrorCtxKey, err)\n\treturn ctx\n}\n\nfunc FromContext(ctx context.Context) (jwt.Token, map[string]interface{}, error) {\n\ttoken, _ := ctx.Value(TokenCtxKey).(jwt.Token)\n\n\tvar err error\n\tclaims := map[string]interface{}{}\n\tif token != nil {\n\t\tif err = transform.AsMap(token, claims); err != nil {\n\t\t\treturn token, nil, err\n\t\t}\n\t}\n\n\terr, _ = ctx.Value(ErrorCtxKey).(error)\n\n\treturn token, claims, err\n}\n\n// UnixTime returns the given time in UTC milliseconds\nfunc UnixTime(tm time.Time) int64 {\n\treturn tm.UTC().Unix()\n}\n\n// EpochNow is a helper function that returns the NumericDate time value used by the spec\nfunc EpochNow() int64 {\n\treturn time.Now().UTC().Unix()\n}\n\n// ExpireIn is a helper function to return calculated time in the future for \"exp\" claim\nfunc ExpireIn(tm time.Duration) int64 {\n\treturn EpochNow() + int64(tm.Seconds())\n}\n\n// Set issued at (\"iat\") to specified time in the claims\nfunc SetIssuedAt(claims map[string]interface{}, tm time.Time) {\n\tclaims[\"iat\"] = tm.UTC().Unix()\n}\n\n// Set issued at (\"iat\") to present time in the claims\nfunc SetIssuedNow(claims map[string]interface{}) {\n\tclaims[\"iat\"] = EpochNow()\n}\n\n// Set expiry (\"exp\") in the claims\nfunc SetExpiry(claims map[string]interface{}, tm time.Time) {\n\tclaims[\"exp\"] = tm.UTC().Unix()\n}\n\n// Set expiry (\"exp\") in the claims to some duration from the present time\nfunc SetExpiryIn(claims map[string]interface{}, tm time.Duration) {\n\tclaims[\"exp\"] = ExpireIn(tm)\n}\n\n// TokenFromCookie tries to retrieve the token string from a cookie named\n// \"jwt\".\nfunc TokenFromCookie(r *http.Request) string {\n\tcookie, err := r.Cookie(\"jwt\")\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\treturn cookie.Value\n}\n\n// TokenFromHeader tries to retrieve the token string from the\n// \"Authorization\" request header: \"Authorization: BEARER T\".\nfunc TokenFromHeader(r *http.Request) string {\n\t// Get token from authorization header.\n\tbearer := r.Header.Get(\"Authorization\")\n\tif len(bearer) > 7 && strings.ToUpper(bearer[0:7]) == \"BEARER \" {\n\t\treturn bearer[7:]\n\t}\n\treturn \"\"\n}\n\n// TokenFromQuery tries to retrieve the token string from the \"jwt\" URI\n// query parameter.\n//\n// To use it, build our own middleware handler, such as:\n//\n//\tfunc Verifier(ja *JWTAuth) func(http.Handler) http.Handler {\n//\t\treturn func(next http.Handler) http.Handler {\n//\t\t\treturn Verify(ja, TokenFromQuery, TokenFromHeader, TokenFromCookie)(next)\n//\t\t}\n//\t}\nfunc TokenFromQuery(r *http.Request) string {\n\t// Get token from query param named \"jwt\".\n\treturn r.URL.Query().Get(\"jwt\")\n}\n\n// contextKey is a value for use with context.WithValue. It's used as\n// a pointer so it fits in an interface{} without allocation. This technique\n// for defining context keys was copied from Go 1.7's new use of context in net/http.\ntype contextKey struct {\n\tname string\n}\n\nfunc (k *contextKey) String() string {\n\treturn \"jwtauth context value \" + k.name\n}\n"
  },
  {
    "path": "jwtauth_test.go",
    "content": "package jwtauth_test\n\nimport (\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/jwtauth/v5\"\n\t\"github.com/lestrrat-go/jwx/v3/jwa\"\n\t\"github.com/lestrrat-go/jwx/v3/jwt\"\n\t\"github.com/lestrrat-go/jwx/v3/transform\"\n)\n\nvar (\n\tTokenAuthHS256 *jwtauth.JWTAuth\n\tTokenSecret    = []byte(\"secretpass\")\n\n\tTokenAuthRS256 *jwtauth.JWTAuth\n\n\tPrivateKeyRS256String = `-----BEGIN RSA PRIVATE KEY-----\nMIICXgIBAAKBgQDsH6T+WrdRLKHEhbhbnItRo7X5tj0xssOSCJUiZbCHr52XftIr\nhBD6HxbGaKUEzuaCDYGEcQZZRJ1KHfYmJtXPCz4Zp3qlhjNugvTaZoFtQ8RqiWVY\ncHqCY6cmI+3cq2mVrd7MstpXKhC8dZ2MZnzx/zqaeiV21SiwxHed8LmWmQIDAQAB\nAoGAff9I0L1hkrxJOg/M133KTe8Y3L4lG07z0wonYmp274CDjGKNDdF0KbPLOGaA\nn/czw3Qnh5+0LpBRikpAng0dC06z0YnyzrkoPPawC4s2zJeY3NnajK9IfRAAVlby\ncIJVmEL/xF3FFHhCfrJNWd+zthcHxCATJOBpH2pwhb4WLfECQQD/geZ/B6p8WlGb\namHFhBd/hQN6cq63RGujf3ecz5H+h4RqFyycaVr3t8QZBBd3O3jRB9FCcan2IxRa\nUoYNGNB9AkEA7JQtfmb0p8cTHiDyV6qb8aNJFWipwQVVMmpaXvfC6Aue5uJiyHnx\niScLsj1ozewCgTvzL7MAsfj0k6qX3c01TQJBAPL2JCdhM8XB4N4Hf+dhHzMcWd1j\nFi6hOjWjrSsI2owNc2Wqmbo2GNF8BlW/ZUz02YLzixJCoVqzqtPkqyHjGcUCQQDk\nmsrbOeFvvo5arrt+uv21oXMdnOVr/xs0fFCXNBLC53fE4z1RO4SKY5CJy41abpR9\nDNERZodlcovjpRTa31CBAkEAw8geqJ1+cfEDZYfJxJigFSwbwoLw6BH+GD4KAEdX\nG1u9SGGYP19eC2mpQei4V5MqAYEbb82bqcebhwg8kAReNQ==\n-----END RSA PRIVATE KEY-----\n`\n\n\tPublicKeyRS256String = `-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDsH6T+WrdRLKHEhbhbnItRo7X5\ntj0xssOSCJUiZbCHr52XftIrhBD6HxbGaKUEzuaCDYGEcQZZRJ1KHfYmJtXPCz4Z\np3qlhjNugvTaZoFtQ8RqiWVYcHqCY6cmI+3cq2mVrd7MstpXKhC8dZ2MZnzx/zqa\neiV21SiwxHed8LmWmQIDAQAB\n-----END PUBLIC KEY-----\n`\n)\n\nfunc init() {\n\tTokenAuthHS256 = jwtauth.New(jwa.HS256().String(), TokenSecret, nil, jwt.WithAcceptableSkew(30*time.Second))\n}\n\n//\n// Tests\n//\n\nfunc TestSimple(t *testing.T) {\n\tr := chi.NewRouter()\n\n\tr.Use(\n\t\tjwtauth.Verifier(TokenAuthHS256),\n\t\tjwtauth.Authenticator(TokenAuthHS256),\n\t)\n\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"welcome\"))\n\t})\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\ttt := []struct {\n\t\tName          string\n\t\tAuthorization string\n\t\tStatus        int\n\t\tResp          string\n\t}{\n\t\t{Name: \"empty token\", Authorization: \"\", Status: 401, Resp: \"no token found\\n\"},\n\t\t{Name: \"wrong token\", Authorization: \"Bearer asdf\", Status: 401, Resp: \"token is unauthorized\\n\"},\n\t\t{Name: \"wrong secret\", Authorization: \"Bearer \" + newJwtToken([]byte(\"wrong\")), Status: 401, Resp: \"token is unauthorized\\n\"},\n\t\t{Name: \"wrong secret/alg\", Authorization: \"Bearer \" + newJwt512Token([]byte(\"wrong\")), Status: 401, Resp: \"token is unauthorized\\n\"},\n\t\t{Name: \"wrong alg\", Authorization: \"Bearer \" + newJwt512Token(TokenSecret, map[string]interface{}{}), Status: 401, Resp: \"token is unauthorized\\n\"},\n\t\t{Name: \"expired within skew\", Authorization: \"Bearer \" + newJwtToken(TokenSecret, map[string]interface{}{\"exp\": time.Now().Unix() - 29}), Status: 200, Resp: \"welcome\"},\n\t\t{Name: \"expired outside skew\", Authorization: \"Bearer \" + newJwtToken(TokenSecret, map[string]interface{}{\"exp\": time.Now().Unix() - 31}), Status: 401, Resp: \"token is expired\\n\"},\n\t\t{Name: \"valid token\", Authorization: \"Bearer \" + newJwtToken(TokenSecret), Status: 200, Resp: \"welcome\"},\n\t\t{Name: \"valid Bearer\", Authorization: \"Bearer \" + newJwtToken(TokenSecret, map[string]interface{}{\"service\": \"test\"}), Status: 200, Resp: \"welcome\"},\n\t\t{Name: \"valid BEARER\", Authorization: \"BEARER \" + newJwtToken(TokenSecret), Status: 200, Resp: \"welcome\"},\n\t\t{Name: \"valid bearer\", Authorization: \"bearer \" + newJwtToken(TokenSecret), Status: 200, Resp: \"welcome\"},\n\t\t{Name: \"valid claim\", Authorization: \"Bearer \" + newJwtToken(TokenSecret, map[string]interface{}{\"service\": \"test\"}), Status: 200, Resp: \"welcome\"},\n\t\t{Name: \"invalid bearer_\", Authorization: \"BEARER_\" + newJwtToken(TokenSecret), Status: 401, Resp: \"no token found\\n\"},\n\t\t{Name: \"invalid bearerx\", Authorization: \"BEARERx\" + newJwtToken(TokenSecret), Status: 401, Resp: \"no token found\\n\"},\n\t}\n\n\tfor _, tc := range tt {\n\t\th := http.Header{}\n\t\tif tc.Authorization != \"\" {\n\t\t\th.Set(\"Authorization\", tc.Authorization)\n\t\t}\n\t\tstatus, resp := testRequest(t, ts, \"GET\", \"/\", h, nil)\n\t\tif status != tc.Status || resp != tc.Resp {\n\t\t\tt.Errorf(\"test '%s' failed: expected Status: %d %q, got %d %q\", tc.Name, tc.Status, tc.Resp, status, resp)\n\t\t}\n\t}\n}\n\nfunc TestSimpleRSA(t *testing.T) {\n\tprivateKeyBlock, _ := pem.Decode([]byte(PrivateKeyRS256String))\n\n\tprivateKey, err := x509.ParsePKCS1PrivateKey(privateKeyBlock.Bytes)\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\n\tpublicKeyBlock, _ := pem.Decode([]byte(PublicKeyRS256String))\n\n\tpublicKey, err := x509.ParsePKIXPublicKey(publicKeyBlock.Bytes)\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\n\tTokenAuthRS256 = jwtauth.New(jwa.RS256().String(), privateKey, publicKey)\n\n\tclaims := map[string]interface{}{\n\t\t\"key\":  \"val\",\n\t\t\"key2\": \"val2\",\n\t\t\"key3\": \"val3\",\n\t}\n\n\t_, tokenString, err := TokenAuthRS256.Encode(claims)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to encode claims %s\\n\", err.Error())\n\t}\n\n\ttoken, err := TokenAuthRS256.Decode(tokenString)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to decode token string %s\\n\", err.Error())\n\t}\n\n\ttokenClaims := map[string]interface{}{}\n\tif err := transform.AsMap(token, tokenClaims); err != nil {\n\t\tt.Fatalf(\"Failed to get claims %s\\n\", err.Error())\n\t}\n\n\tif !reflect.DeepEqual(claims, tokenClaims) {\n\t\tt.Fatalf(\"The decoded claims don't match the original ones\\n\")\n\t}\n}\n\nfunc TestSimpleRSAVerifyOnly(t *testing.T) {\n\ttokenString := \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ2YWwiLCJrZXkyIjoidmFsMiIsImtleTMiOiJ2YWwzIn0.IK0G0Qi_c6N6uHRokHMSHQEeYxoi_T73A4RdEzJIfnbs5kA5hF0UhApSWUMZfsTYFNC2buYvWqbyj2kDdXcStpqTUPENGTKvJi66puwhN16BqEOS-jb7kVyf3vWif7XabY0_5S8H_aeqazaj4FemHvWnywJznuMWJRXWw83edpA\"\n\tclaims := map[string]interface{}{\n\t\t\"key\":  \"val\",\n\t\t\"key2\": \"val2\",\n\t\t\"key3\": \"val3\",\n\t}\n\n\tpublicKeyBlock, _ := pem.Decode([]byte(PublicKeyRS256String))\n\tpublicKey, err := x509.ParsePKIXPublicKey(publicKeyBlock.Bytes)\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\n\tTokenAuthRS256 = jwtauth.New(jwa.RS256().String(), nil, publicKey)\n\n\t_, _, err = TokenAuthRS256.Encode(claims)\n\tif err == nil {\n\t\tt.Fatalf(\"Expecting error when encoding claims without signing key\")\n\t}\n\n\ttoken, err := TokenAuthRS256.Decode(tokenString)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to decode token string %s\\n\", err.Error())\n\t}\n\n\ttokenClaims := map[string]interface{}{}\n\tif err := transform.AsMap(token, tokenClaims); err != nil {\n\t\tt.Fatalf(\"Failed to get claims %s\\n\", err.Error())\n\t}\n\n\tif !reflect.DeepEqual(claims, tokenClaims) {\n\t\tt.Fatalf(\"The decoded claims don't match the original ones\\n\")\n\t}\n}\n\nfunc TestMore(t *testing.T) {\n\tr := chi.NewRouter()\n\n\t// Protected routes\n\tr.Group(func(r chi.Router) {\n\t\tr.Use(jwtauth.Verifier(TokenAuthHS256))\n\n\t\tauthenticator := func(next http.Handler) http.Handler {\n\t\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\ttoken, _, err := jwtauth.FromContext(r.Context())\n\n\t\t\t\tif err != nil {\n\t\t\t\t\thttp.Error(w, jwtauth.ErrorReason(err).Error(), http.StatusUnauthorized)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif err := jwt.Validate(token); err != nil {\n\t\t\t\t\thttp.Error(w, jwtauth.ErrorReason(err).Error(), http.StatusUnauthorized)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// Token is authenticated, pass it through\n\t\t\t\tnext.ServeHTTP(w, r)\n\t\t\t})\n\t\t}\n\t\tr.Use(authenticator)\n\n\t\tr.Get(\"/admin\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\t_, claims, err := jwtauth.FromContext(r.Context())\n\n\t\t\tif err != nil {\n\t\t\t\tw.Write([]byte(fmt.Sprintf(\"error! %v\", err)))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tw.Write([]byte(fmt.Sprintf(\"protected, user:%v\", claims[\"user_id\"])))\n\t\t})\n\t})\n\n\t// Public routes\n\tr.Group(func(r chi.Router) {\n\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.Write([]byte(\"welcome\"))\n\t\t})\n\t})\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\t// sending unauthorized requests\n\tif status, resp := testRequest(t, ts, \"GET\", \"/admin\", nil, nil); status != 401 || resp != \"token is unauthorized\\n\" {\n\t\tt.Fatal(resp)\n\t}\n\n\th := http.Header{}\n\th.Set(\"Authorization\", \"BEARER \"+newJwtToken([]byte(\"wrong\"), map[string]interface{}{}))\n\tif status, resp := testRequest(t, ts, \"GET\", \"/admin\", h, nil); status != 401 || resp != \"token is unauthorized\\n\" {\n\t\tt.Fatal(resp)\n\t}\n\th.Set(\"Authorization\", \"BEARER asdf\")\n\tif status, resp := testRequest(t, ts, \"GET\", \"/admin\", h, nil); status != 401 || resp != \"token is unauthorized\\n\" {\n\t\tt.Fatal(resp)\n\t}\n\t// wrong token secret and wrong alg\n\th.Set(\"Authorization\", \"BEARER \"+newJwt512Token([]byte(\"wrong\"), map[string]interface{}{}))\n\tif status, resp := testRequest(t, ts, \"GET\", \"/admin\", h, nil); status != 401 || resp != \"token is unauthorized\\n\" {\n\t\tt.Fatal(resp)\n\t}\n\t// correct token secret but wrong alg\n\th.Set(\"Authorization\", \"BEARER \"+newJwt512Token(TokenSecret, map[string]interface{}{}))\n\tif status, resp := testRequest(t, ts, \"GET\", \"/admin\", h, nil); status != 401 || resp != \"token is unauthorized\\n\" {\n\t\tt.Fatal(resp)\n\t}\n\n\th = newAuthHeader(map[string]interface{}{\"exp\": jwtauth.EpochNow() - 1000})\n\tif status, resp := testRequest(t, ts, \"GET\", \"/admin\", h, nil); status != 401 || resp != \"token is expired\\n\" {\n\t\tt.Fatal(resp)\n\t}\n\n\t// sending authorized requests\n\tif status, resp := testRequest(t, ts, \"GET\", \"/\", nil, nil); status != 200 || resp != \"welcome\" {\n\t\tt.Fatal(resp)\n\t}\n\n\th = newAuthHeader((map[string]interface{}{\"user_id\": 31337, \"exp\": jwtauth.ExpireIn(5 * time.Minute)}))\n\tif status, resp := testRequest(t, ts, \"GET\", \"/admin\", h, nil); status != 200 || resp != \"protected, user:31337\" {\n\t\tt.Fatal(resp)\n\t}\n}\n\nfunc TestEncodeClaims(t *testing.T) {\n\tclaims := map[string]interface{}{\n\t\t\"key1\": \"val1\",\n\t\t\"key2\": 2,\n\t\t\"key3\": time.Now(),\n\t\t\"key4\": []string{\"1\", \"2\"},\n\t}\n\tclaims[jwt.JwtIDKey] = 1\n\tif _, _, err := TokenAuthHS256.Encode(claims); err == nil {\n\t\tt.Fatal(\"encoding invalid claims succeeded\")\n\t}\n\tclaims[jwt.JwtIDKey] = \"123\"\n\tif _, _, err := TokenAuthHS256.Encode(claims); err != nil {\n\t\tt.Fatalf(\"unexpected error encoding valid claims: %v\", err)\n\t}\n}\n\n//\n// Test helper functions\n//\n\nfunc testRequest(t *testing.T, ts *httptest.Server, method, path string, header http.Header, body io.Reader) (int, string) {\n\treq, err := http.NewRequest(method, ts.URL+path, body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn 0, \"\"\n\t}\n\n\tfor k, v := range header {\n\t\treq.Header.Set(k, v[0])\n\t}\n\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn 0, \"\"\n\t}\n\n\trespBody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn 0, \"\"\n\t}\n\tdefer resp.Body.Close()\n\n\treturn resp.StatusCode, string(respBody)\n}\n\nfunc newJwtToken(secret []byte, claims ...map[string]interface{}) string {\n\ttoken := jwt.New()\n\tif len(claims) > 0 {\n\t\tfor k, v := range claims[0] {\n\t\t\ttoken.Set(k, v)\n\t\t}\n\t}\n\n\ttokenPayload, err := jwt.Sign(token, jwt.WithKey(jwa.HS256(), secret))\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\treturn string(tokenPayload)\n}\n\nfunc newJwt512Token(secret []byte, claims ...map[string]interface{}) string {\n\t// use-case: when token is signed with a different alg than expected\n\ttoken := jwt.New()\n\tif len(claims) > 0 {\n\t\tfor k, v := range claims[0] {\n\t\t\ttoken.Set(k, v)\n\t\t}\n\t}\n\ttokenPayload, err := jwt.Sign(token, jwt.WithKey(jwa.HS512(), secret))\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\treturn string(tokenPayload)\n}\n\nfunc newAuthHeader(claims ...map[string]interface{}) http.Header {\n\th := http.Header{}\n\th.Set(\"Authorization\", \"BEARER \"+newJwtToken(TokenSecret, claims...))\n\treturn h\n}\n"
  }
]