go-chi / jwtauth Goto Github PK
View Code? Open in Web Editor NEWJWT authentication middleware for Go HTTP services
License: MIT License
JWT authentication middleware for Go HTTP services
License: MIT License
The middleare specifically states: We explicitly toggle SkipClaimsValidation in the
jwt-go parser so that we can control when the claims are validated - in our case, by the Verifier http middleware handler.
https://github.com/go-chi/jwtauth/blob/master/jwtauth.go#L42-L46
Then, in the Verifier middleware, the code doesn't ever actually validate the claims at all:
https://github.com/go-chi/jwtauth/blob/master/jwtauth.go#L146-L150
https://github.com/go-chi/jwtauth/blob/master/jwtauth.go#L109-L130
The best it does it check the exp
field manually, using code that doesn't need to exist since jwt-go
already has this functionality built in.
My suggestion is to stop skipping claims validation, and stop checking the exp
claim manually, and instead let jwt-go
verify the exp
claim along with the nbf
and iat
claims.
If you agree, I can make a pull request.
The kid
claim in the header can be used to specify which key was used to secure the token.
Please add support for this.
package routes
import (
"alexapp.pck.com/handler"
"fmt"
"github.com/go-chi/chi"
"github.com/go-chi/jwtauth"
"net/http"
)
var tokenAuth *jwtauth.JWTAuth
//InitIndexRoutes function
func InitRestrictedRoutes(c *chi.Mux, h *handler.Handler) {
// Protected routes
c.Group(func(r chi.Router) {
// Seek, verify and validate JWT tokens
r.Use(jwtauth.Verifier(tokenAuth))
// Handle valid / invalid tokens. In this example, we use
// the provided authenticator middleware, but you can write your
// own very easily, look at the Authenticator method in jwtauth.go
// and tweak it, its not scary.
r.Use(jwtauth.Authenticator)
r.Get("/restricted", func(w http.ResponseWriter, r *http.Request) {
_, claims, _ := jwtauth.FromContext(r.Context())
w.Write([]byte(fmt.Sprintf("protected area. hi %v", claims["user_id"])))
})
})
}
It seems that for a reason the var tokenAuth *jwtauth.JWTAuth is of value nil in the actual jwtauth.Verifier(tokenAuth) code.
So this gives me the error :
2019/09/10 21:39:07 http: panic serving [::1]:53185: runtime error: invalid memory address or nil pointer dereference
goroutine 103 [running]:
net/http.(*conn).serve.func1(0xc00029c320)
/usr/local/go/src/net/http/server.go:1769 +0x139
panic(0x1392660, 0x16ea470)
/usr/local/go/src/runtime/panic.go:522 +0x1b5
github.com/go-chi/jwtauth.(*JWTAuth).Decode(0x0, 0xc0001cc547, 0x65, 0x10, 0x10, 0xc00005ba88)
/src/github.com/go-chi/jwtauth/jwtauth.go:143 +0x26
github.com/go-chi/jwtauth.VerifyRequest(0x0, 0xc000169000, 0xc000246660, 0x3, 0x3, 0x0, 0xc0001e5f20, 0x0)
/src/github.com/go-chi/jwtauth/jwtauth.go:106 +0x94
github.com/go-chi/jwtauth.Verify.func1.1(0x1483080, 0xc00025cfc0, 0xc000169000)
/src/github.com/go-chi/jwtauth/jwtauth.go:80 +0x98
In the actual example, you have:
var tokenAuth *jwtauth.JWTAuth
//And you assign a value to it in the init() function :
// tokenAuth = jwtauth.New("HS256", []byte("secret"), nil)
In my case, I already have the token generated and saved somewhere else and all I want to do is use that token.
So when the code arrives in my initRestrictedFunction , what should the value for : tokenAuth be ?
On the line: r.Use(jwtauth.Verifier(tokenAuth))
Thanks
Currently I have 2 services:
Somehow I end up getting error 401 unauthorized when passing the tokens from one service to another, why is this happening?
I'm just wondering why it behaves like this. Although the easiest way is to implement an api gateway to solve this, I'd like to know if theres something wrong with my approach.
Hi all,
I am using chi for tests purposes, but I did not found any example that validates a jwt.MapClaims with expiration time.
I am using this:
var TokenAuth = jwtauth.New(JWTAlgorithm, JWTSecret, nil)
// Transaction paths
r.Group(func(r chi.Router) {
r.Use(jwtauth.Verifier(TokenAuth))
r.Use(jwtauth.Authenticator)
_transactionHttpHandler.NewTransactionHandler(r, transactionUsecase)
})
And generate JWT with this:
func generateJWT(c *model.Client) (token string, err error) {
_, token, err = TokenAuth.Encode(jwt.MapClaims{"clientId": c.ID.String()})
if err != nil {
return "", err
}
return token, nil
}
But my token has no expires time and I do not know to do this with this package. Someone can help?
Thanks :)
In VerifyRequest the conditional statement checking the error returned by JWTAuth.Decode is performed against the wrong constant (assuming the comments in the code are correct)
// lines 113-115 of jwtauth.go
} else if verr.Errors&jwt.ValidationErrorIssuedAt > 0 {
return token, ErrNBFInvalid
}
should be
} else if verr.Errors&jwt.ValidationErrorNotValidYet > 0 {
return token, ErrNBFInvalid
}
If this is not a bug (I haven't read the spec to know the difference between these errors) then my apologies for opening. I can send a PR over to address if interested.
Thanks
Running the example gives the following error:
# github.com/goware/jwtauth
../github.com/goware/jwtauth/jwtauth.go:151: cannot use claims (type Claims) as type jwt.Claims in assignment:
Claims does not implement jwt.Claims (missing Valid method)
../github.com/goware/jwtauth/jwtauth.go:173: invalid operation: t.Claims["exp"] (type jwt.Claims does not support indexing)
../github.com/goware/jwtauth/jwtauth.go:175: use of .(type) outside type switch
Take a look at https://jwt.io/
Whould be nice to change the jwt package to use github.com/SermoDigital/jose.
Casting token pointer value retrieved from request context fails for no obvious reason:
func accCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
val := ctx.Value("jwt")
log.Printf("%#v\n", val)
t, ok := val.(*jwt.Token)
if !ok {
errBadRequest(w)
return
}
newctx := context.WithValue(ctx, ctxAccountID, t.Claims["account_id"])
next.ServeHTTP(w, r.WithContext(newctx))
})
}
The Printf call above displays something that looks like valid jwt.Token reference:
&jwt.Token{Raw:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50X2lkIjoiYTIxZjE4YjQtMGI0Mi00ZDdhLWJkNDAtMmI0ZTZkNGZlYTdjIiwidXNlcl9pZCI6ImQxMWQwOTZkLTc0N2ItNGYyNS05NjcxLWJhNzJjNjM3NTY0YSJ9.ERX3v2mVL_AQu3_nYj7e4xkeXse6VNKR_FcZm8qvQ-A", Method:(*jwt.SigningMethodHMAC)(0xc4200ce760), Header:map[string]interface {}{"alg":"HS256", "typ":"JWT"}, Claims:map[string]interface {}{"account_id":"a21f18b4-0b42-4d7a-bd40-2b4e6d4fea7c", "user_id":"d11d096d-747b-4f25-9671-ba72c637564a"}, Signature:"ERX3v2mVL_AQu3_nYj7e4xkeXse6VNKR_FcZm8qvQ-A", Valid:true}
However, casting that value to *jwt.Token fails.
I'd like to have the possibility to have different clients use different secrets - how would I test against multiple secrets on the server side?
Thanks!
The JWT look like this:
{
"iss": "...",
"dest": "...",
"aud": "...",
"sub": "...",
"exp": 1609818402, //-> 01/05/2021 @ 3:46:42am (UTC)
"nbf": 1609818342, //-> 01/05/2021 @ 3:45:42am (UTC)
"iat": 1609818342, //-> 01/05/2021 @ 3:45:42am (UTC)
"jti": "...",
"sid": "..."
}
I dev apps for a service (Shopify) that lets you request a JWT from their servers, and then you can use that JWT to make authenticated calls to your backend (since the token is signed with your API secret, which both you and Shopify have on hand). My backend uses this JWT middleware to handle that part.
The issue I'm having is that it seems if I request a JWT and immediately use it to make a call to my backend (we're talking sub 50 milliseconds between getting the JWT and making a request to my backend), the JWT middleware returns the following error: token nbf validation failed
If I then use a setTimeout()
function on my frontend to add a 750-1000ms delay after requesting the JWT, the issue does not happen. So it seems to me like there's something going on with the underlying library/implementation that causes the backend to think it hasn't reached the correct NBF time yet?
Not sure how I would go about resolving this other than manually adding delays before making calls to my backend. Suggestions?
I have regular and refresh token. At the moment user can pass verification with both tokens.
I want check token's type on Verify and allow refresh token only for Refresh procedure.
Is it real?
Hi. I'm trying to run the example:
r.Get("/secret", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
token := ctx.Value("jwt").(*jwt.Token)
render.JSON(w, r, token)
})
and get the error panic: interface conversion: interface {} is *jwt.Token, not *jwt.Token
I'm a little bit confused, because if I try to run reflect.typeOf(token)
- token has type *jwt.Token
.
Could you say please, is there a way to solve this?
P.S.
Also, I'm tried to vendor dgrijalva/jwt-go 2.7.0
package, but got the same error
Line 64 in c5c2c95
According to the doc of the function, the verifier should search through URI query parameters.
However, the TokenFromQuery
function is not called in the verifier
// Verifier will search for a JWT token in a http request, in the order:
// 1. 'jwt' URI query parameter
// 2. 'Authorization: BEARER T' request header
// 3. Cookie 'jwt' value
https://github.com/go-chi/jwtauth/blob/master/jwtauth.go#L112 seems to not be catching anything, as jwt-go
does not have this error (anymore).
I'm getting a very strange error using the sample code from jwtauth. The error is at this line:
token := ctx.Value("jwt").(*jwt.Token)
Solved this issue after remove the vendor folder inside jwtauth project.
Keeping the vendor in a library project isn't a good idea, its better to remove it, the front page of the project has a very big warning about the dependency, its better to handle the dependencies at the application level.
Hi!
I've been working on an extension for echo's JWT middleware to enable discovery through OpenID Connect. Everything using the jwx library.
Would you be interested in a PR for the same thing here?
You can see the PR here: labstack/echo-contrib#59
If you are interested, I'd love some feedback on how you would prefer the API to be.
Thanks!
Regards,
Simon
Hello,
I want to know if it is possible to use this library to verify an ID Token originating from Auth0.
I only need the identity of the caller, the authorization is done inside the application.
The readme says I need to pass in a secret. However, I don't know what secret is meant. I have tried it with the "Client Secret". It will error with "token is unauthorized".
tokenAuth = jwtauth.New("HS256", []byte("my client secret"), nil)
Thank you very much for this package and for any help!
Is it possible to use jwtauth with the RSA signing method?
From my understanding of the jwt-go library, its SignedString
method takes the key as interface{}
func (t *Token) SignedString(key interface{}) (string, error) {
var sig, sstr string
var err error
if sstr, err = t.SigningString(); err != nil {
return "", err
}
if sig, err = t.Method.Sign(sstr, key); err != nil {
return "", err
}
return strings.Join([]string{sstr, sig}, "."), nil
}
And each different signing method implementation is free to choose the type of the key. However in go-chi/jwtauth, the keys are only allowed to be []byte
. This works fine for the HMAC implementation, which expects the key to be a []byte
, but the RSA implementation expects the key to be of type *rsa.PrivateKey
func (m *SigningMethodRSA) Sign(signingString string, key interface{}) (string, error) {
var rsaKey *rsa.PrivateKey
var ok bool
// Validate type of key
if rsaKey, ok = key.(*rsa.PrivateKey); !ok {
return "", ErrInvalidKey
}
// Create the hasher
if !m.Hash.Available() {
return "", ErrHashUnavailable
}
hasher := m.Hash.New()
hasher.Write([]byte(signingString))
// Sign the string and return the encoded bytes
if sigBytes, err := rsa.SignPKCS1v15(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil)); err == nil {
return EncodeSegment(sigBytes), nil
} else {
return "", err
}
}
This results in a "key is invalid type" error when using the RSA signing method. Check this issue for more info dgrijalva/jwt-go#65.
I think it would be nice to make the token search order configureable. Currently the middleware searches for a query parameter first. In my case, I would like to search for the token in the Authorization
header only! Sure, I could create yet another fork, but maybe others want the same functionality. If this is desired, we could discuss this and I would look into submitting a PR.
The new underlying JWT library supports options for validation via jwt.Validate(t Token, options ...ValidateOption)
, unfortunately this isn't exposed in jwtauth
.
jwtauth.Verifier(ja *JWTAuth)
calls down the verification stack until jwtauth.VerifyToken(ja *JWTAuth, tokenString string)
where in the body there is a call to jwt.Validate(t Token, options ...ValidateOption)
, unfortunately I can't see a way to alter these options from the jwtauth API though?
Meanwhile the jwtauth.Authenticator(next http.Handler)
example also calls jwt.Validate(...)
(i.e. a second call to this function in the request sequence) where obviously it's possible to add the validation options using a custom Authenticator.
Is the expectation that a second call to jwt.Validate(...)
with or without options parameters will be needed in any custom Authenticator? It looks like if I could pass the options in I could avoid calling the function again.
module.go
func (oM *OracleModule) Init(ctx context.Context, serverConfig serverConfig.ServerConfig) {
// token init
oM.tokenAuth = jwtauth.New("HS256", []byte(oM.config.JwtSecretKey), nil)
}
user.go
tokenString := oM.GenerateJwtToken(respBody["email"].(string), respBody["name"].(string))
Here the tokenString is giving same token everytime. Is this expected?
func (ja *JWTAuth) Encode(claims map[string]interface{}) (t jwt.Token, tokenString string, err error) {
t = jwt.New()
for k, v := range claims {
t.Set(k, v)
}
payload, err := ja.sign(t)
if err != nil {
return nil, "", err
}
tokenString = string(payload)
return
}
If t.Set(k,v) returns error, we should handle it and error out earlier, instead of now just ignore it ?
Look at the API t.Set(k, v), depending on the key's name, it may fail if we provide a wrong type for that key, e.g. the sub
has to be string.
When I use jwtauth.Authenticator, it works.
But when I copy the same Authenticator to my own package, an error occur at
if !ok || jwtToken == nil || !jwtToken.Valid {
http.Error(w, http.StatusText(401), 401)
return
}
Any clues?
At least with Go 1.13, I am not able to use 4.0.x versions. It's the same problem that chi v4 has, but there are changes in v4 here that I'm relying on. It looks like chi v4 was just an experiment in moduling.
Hi all. https://github.com/dgrijalva/jwt-go has had a security issue open for a while now (dgrijalva/jwt-go#428, dgrijalva/jwt-go#422) about the aud
field. I and several other devs have reached out to the maintainer about merging a fix PR. However, we haven't gotten a reply. The repo hasn't seen any activity since January and I suspect it's no longer maintained.
My suggestion is to move to a fork from Form3, https://github.com/form3tech-oss/jwt-go/
Let's check the conventional "exp" claim field for the value to check if the current time past the present time. If so, we should return a 401.
Hello.
I'm trying to get data from claims, after the decode. But it seems that the original value (an int) turns a float64, after the decode.
Example:
package main
import (
"context"
"fmt"
"github.com/go-chi/jwtauth/v5"
)
func main() {
tokenAuth := jwtauth.New("HS256", []byte("secret"), nil)
_, tokenString, _ := tokenAuth.Encode(map[string]interface{}{"user_id": 123})
myToken, _ := tokenAuth.Decode(tokenString)
claims, _ := myToken.AsMap(context.Background())
userID := claims["user_id"]
_, ok := userID.(int)
if ok {
fmt.Printf("userID %v is int\n", userID)
}
_, ok = userID.(float64)
if ok {
fmt.Printf("userID %v is float\n", userID)
}
}
When I run this, I got "userID 123 is float".
I'm using go 1.16.6, on ubuntu 21.04.
I am doing something wrong?
Thanks
Eventhough , I set the exp time to 2 hours from now(). the Authenticator method throwing error.
looks like this repository is dead... no maintenance since jan.
Hey, I just started using this awesome libary and I have a "basic" question.
I want to have jwt tokens with an expiration date. My first thought was to handle this through a second claim. Then I had a look at jwtauth.go and saw, that there is a possible error regarding to the expiration date. But I did not find an option to set the expiration date for a jwt token generated with TokenAuth.Encode() function.
I would be glad to get a reply.
Thanks,
Thecrafterja
I'm doing the oauth dance with an API (Shopify) that is making it difficult on me to write an authenticator function. Currently it looks like this:
func Authenticator(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token, _, err := jwtauth.FromContext(r.Context())
if err != nil {
ctx := context.WithValue(r.Context(), "shop", r.URL.Query().Get("shop"))
fmt.Println(r.URL.Query().Get("shop"), "Debug: 1")
http.Redirect(w, r.WithContext(ctx), "http://localhost:2020/login", 301)
}
if token == nil || !token.Valid {
ctx := context.WithValue(r.Context(), "shop", r.URL.Query().Get("shop"))
fmt.Println(r.URL.Query().Get("shop"), "Debug: 2")
http.Redirect(w, r.WithContext(ctx), "http://localhost:2020/login", 301)
}
// Token is authenticated, pass it through
next.ServeHTTP(w, r)
})
}
If I'm not mistaken, the middleware runs before the actual request right? Which means that my r.URL.Query().Get("shop")
will never be available to the middleware before redirecting to http://localhost:2020/login.
But it's necessary because shopify doesn't auth with a generic https://shopify.com/oauth/authorize style endpoint, but rather with a account specific endpoint that looks like this: https://{shop}.myshopify.com/admin/oauth/authorize
. The shop query param I'm trying to pass in would replace that {shop}
in the URL and is provided to the code when you go to http://localhost:2020/?shop=shopNameHere.myshopify.com
. The authenticator function should redirect to /login
and pass the 'shop' param through but it doesn't.
So what can I do here?
When I try to use jwt.Validate
in my application, it's always be error.
http: panic serving [::1]:60976: runtime error: invalid memory address or nil pointer dereference
goroutine 45 [running]:
I tried to use an other custom middleware but still got an error from this function, please help!
"GET http://localhost:****/track HTTP/1.1" from [::1]:64115 - 000 0B in 297.334µs
2023/06/28 23:54:48 http: panic serving [::1]:64115: runtime error: invalid memory address or nil pointer dereference
goroutine 36 [running]:
net/http.(*conn).serve.func1()
/usr/local/go/src/net/http/server.go:1850 +0xbf
panic({0x1372980, 0x1675fd0})
/usr/local/go/src/runtime/panic.go:890 +0x262
github.com/go-chi/jwtauth/v5.(*JWTAuth).parse(0x0, {0xc00024a0b0, 0xac, 0xb0})
~/go/pkg/mod/github.com/go-chi/jwtauth/[email protected]/jwtauth.go:140 +0xb6
github.com/go-chi/jwtauth/v5.(*JWTAuth).Decode(...)
~/go/pkg/mod/github.com/go-chi/jwtauth/[email protected]/jwtauth.go:131
github.com/go-chi/jwtauth/v5.VerifyToken(0xc000099838?, {0xc0002340c5?, 0x10?})
~/go/pkg/mod/github.com/go-chi/jwtauth/[email protected]/jwtauth.go:100 +0x3c
github.com/go-chi/jwtauth/v5.VerifyRequest(0x0?, 0x0?, {0xc0001192d0, 0x2, 0x0?})
~/go/pkg/mod/github.com/go-chi/jwtauth/[email protected]/jwtauth.go:95 +0xac
github.com/go-chi/jwtauth/v5.Verify.func1.1({0x29d75128, 0xc00021e300}, 0xc000224500)
~/go/pkg/mod/github.com/go-chi/jwtauth/[email protected]/jwtauth.go:71 +0x89
net/http.HandlerFunc.ServeHTTP(0xc0001d2000?, {0x29d75128?, 0xc00021e300?}, 0xc00023c240?)
/usr/local/go/src/net/http/server.go:2109 +0x2f
github.com/go-chi/chi/v5.(*ChainHandler).ServeHTTP(0x13707e0?, {0x29d75128?, 0xc00021e300?}, 0xc000226034?)
~/go/pkg/mod/github.com/go-chi/chi/[email protected]/chain.go:31 +0x2c
github.com/go-chi/chi/v5.(*Mux).routeHTTP(0xc00012a300, {0x29d75128, 0xc00021e300}, 0xc000224500)
~/go/pkg/mod/github.com/go-chi/chi/[email protected]/mux.go:444 +0x216
net/http.HandlerFunc.ServeHTTP(0xc000224400?, {0x29d75128?, 0xc00021e300?}, 0xd0?)
/usr/local/go/src/net/http/server.go:2109 +0x2f
github.com/go-chi/chi/v5/middleware.RequestLogger.func1.1({0x14814d0, 0xc00022a2a0}, 0xc000224400)
~/go/pkg/mod/github.com/go-chi/chi/[email protected]/middleware/logger.go:54 +0x17d
net/http.HandlerFunc.ServeHTTP(0x1481998?, {0x14814d0?, 0xc00022a2a0?}, 0x1675e00?)
/usr/local/go/src/net/http/server.go:2109 +0x2f
github.com/go-chi/chi/v5.(*Mux).ServeHTTP(0xc00012a300, {0x14814d0, 0xc00022a2a0}, 0xc000224300)
~/go/pkg/mod/github.com/go-chi/chi/[email protected]/mux.go:90 +0x310
net/http.serverHandler.ServeHTTP({0xc0002142a0?}, {0x14814d0, 0xc00022a2a0}, 0xc000224300)
/usr/local/go/src/net/http/server.go:2947 +0x30c
net/http.(*conn).serve(0xc0002180a0, {0x1481a40, 0xc0001d2720})
/usr/local/go/src/net/http/server.go:1991 +0x607
created by net/http.(*Server).Serve
/usr/local/go/src/net/http/server.go:3102 +0x4db
2023/06/28 23:54:48 "GET http://localhost:****/track HTTP/1.1" from [::1]:64120 - 000 0B in 152.666µs
2023/06/28 23:54:48 http: panic serving [::1]:64120: runtime error: invalid memory address or nil pointer dereference
goroutine 25 [running]:
net/http.(*conn).serve.func1()
/usr/local/go/src/net/http/server.go:1850 +0xbf
panic({0x1372980, 0x1675fd0})
/usr/local/go/src/runtime/panic.go:890 +0x262
github.com/go-chi/jwtauth/v5.(*JWTAuth).parse(0x0, {0xc000154210, 0xac, 0xb0})
~/go/pkg/mod/github.com/go-chi/jwtauth/[email protected]/jwtauth.go:140 +0xb6
github.com/go-chi/jwtauth/v5.(*JWTAuth).Decode(...)
~/go/pkg/mod/github.com/go-chi/jwtauth/[email protected]/jwtauth.go:131
github.com/go-chi/jwtauth/v5.VerifyToken(0xc000068838?, {0xc0001dc0c5?, 0x10?})
~/go/pkg/mod/github.com/go-chi/jwtauth/[email protected]/jwtauth.go:100 +0x3c
github.com/go-chi/jwtauth/v5.VerifyRequest(0x0?, 0x0?, {0xc0001192d0, 0x2, 0x0?})
~/go/pkg/mod/github.com/go-chi/jwtauth/[email protected]/jwtauth.go:95 +0xac
github.com/go-chi/jwtauth/v5.Verify.func1.1({0x29d75128, 0xc0001d64c0}, 0xc00019e700)
~/go/pkg/mod/github.com/go-chi/jwtauth/[email protected]/jwtauth.go:71 +0x89
net/http.HandlerFunc.ServeHTTP(0xc0001d2000?, {0x29d75128?, 0xc0001d64c0?}, 0xc0001254f0?)
/usr/local/go/src/net/http/server.go:2109 +0x2f
github.com/go-chi/chi/v5.(*ChainHandler).ServeHTTP(0x13707e0?, {0x29d75128?, 0xc0001d64c0?}, 0xc000132274?)
~/go/pkg/mod/github.com/go-chi/chi/[email protected]/chain.go:31 +0x2c
github.com/go-chi/chi/v5.(*Mux).routeHTTP(0xc00012a300, {0x29d75128, 0xc0001d64c0}, 0xc00019e700)
~/go/pkg/mod/github.com/go-chi/chi/[email protected]/mux.go:444 +0x216
net/http.HandlerFunc.ServeHTTP(0xc00019e600?, {0x29d75128?, 0xc0001d64c0?}, 0xd0?)
/usr/local/go/src/net/http/server.go:2109 +0x2f
github.com/go-chi/chi/v5/middleware.RequestLogger.func1.1({0x14814d0, 0xc000192380}, 0xc00019e600)
~/go/pkg/mod/github.com/go-chi/chi/[email protected]/middleware/logger.go:54 +0x17d
net/http.HandlerFunc.ServeHTTP(0x1481998?, {0x14814d0?, 0xc000192380?}, 0x1675e00?)
/usr/local/go/src/net/http/server.go:2109 +0x2f
github.com/go-chi/chi/v5.(*Mux).ServeHTTP(0xc00012a300, {0x14814d0, 0xc000192380}, 0xc00019e500)
~/go/pkg/mod/github.com/go-chi/chi/[email protected]/mux.go:90 +0x310
net/http.serverHandler.ServeHTTP({0xc0001d2a80?}, {0x14814d0, 0xc000192380}, 0xc00019e500)
/usr/local/go/src/net/http/server.go:2947 +0x30c
net/http.(*conn).serve(0xc0001b1220, {0x1481a40, 0xc0001d2720})
/usr/local/go/src/net/http/server.go:1991 +0x607
created by net/http.(*Server).Serve
/usr/local/go/src/net/http/server.go:3102 +0x4db
2023/06/28 23:54:48 "GET http://localhost:****/track HTTP/1.1" from [::1]:64121 - 000 0B in 1.013041ms
2023/06/28 23:54:48 http: panic serving [::1]:64121: runtime error: invalid memory address or nil pointer dereference
goroutine 6 [running]:
net/http.(*conn).serve.func1()
/usr/local/go/src/net/http/server.go:1850 +0xbf
panic({0x1372980, 0x1675fd0})
/usr/local/go/src/runtime/panic.go:890 +0x262
github.com/go-chi/jwtauth/v5.(*JWTAuth).parse(0x0, {0xc000298160, 0xac, 0xb0})
~/go/pkg/mod/github.com/go-chi/jwtauth/[email protected]/jwtauth.go:140 +0xb6
github.com/go-chi/jwtauth/v5.(*JWTAuth).Decode(...)
~/go/pkg/mod/github.com/go-chi/jwtauth/[email protected]/jwtauth.go:131
github.com/go-chi/jwtauth/v5.VerifyToken(0xc00006e838?, {0xc00028e0c5?, 0x10?})
~/go/pkg/mod/github.com/go-chi/jwtauth/[email protected]/jwtauth.go:100 +0x3c
github.com/go-chi/jwtauth/v5.VerifyRequest(0x0?, 0x0?, {0xc0001192d0, 0x2, 0x0?})
~/go/pkg/mod/github.com/go-chi/jwtauth/[email protected]/jwtauth.go:95 +0xac
github.com/go-chi/jwtauth/v5.Verify.func1.1({0x29d75128, 0xc00007c3c0}, 0xc000284500)
~/go/pkg/mod/github.com/go-chi/jwtauth/[email protected]/jwtauth.go:71 +0x89
net/http.HandlerFunc.ServeHTTP(0xc0001d2000?, {0x29d75128?, 0xc00007c3c0?}, 0xc000294240?)
/usr/local/go/src/net/http/server.go:2109 +0x2f
github.com/go-chi/chi/v5.(*ChainHandler).ServeHTTP(0x13707e0?, {0x29d75128?, 0xc00007c3c0?}, 0xc00002a10c?)
~/go/pkg/mod/github.com/go-chi/chi/[email protected]/chain.go:31 +0x2c
github.com/go-chi/chi/v5.(*Mux).routeHTTP(0xc00012a300, {0x29d75128, 0xc00007c3c0}, 0xc000284500)
~/go/pkg/mod/github.com/go-chi/chi/[email protected]/mux.go:444 +0x216
net/http.HandlerFunc.ServeHTTP(0xc000284400?, {0x29d75128?, 0xc00007c3c0?}, 0xd0?)
/usr/local/go/src/net/http/server.go:2109 +0x2f
github.com/go-chi/chi/v5/middleware.RequestLogger.func1.1({0x14814d0, 0xc0002882a0}, 0xc000284400)
~/go/pkg/mod/github.com/go-chi/chi/[email protected]/middleware/logger.go:54 +0x17d
net/http.HandlerFunc.ServeHTTP(0x1481998?, {0x14814d0?, 0xc0002882a0?}, 0x1675e00?)
/usr/local/go/src/net/http/server.go:2109 +0x2f
github.com/go-chi/chi/v5.(*Mux).ServeHTTP(0xc00012a300, {0x14814d0, 0xc0002882a0}, 0xc000284300)
~/go/pkg/mod/github.com/go-chi/chi/[email protected]/mux.go:90 +0x310
net/http.serverHandler.ServeHTTP({0xc00007e240?}, {0x14814d0, 0xc0002882a0}, 0xc000284300)
/usr/local/go/src/net/http/server.go:2947 +0x30c
net/http.(*conn).serve(0xc0000000a0, {0x1481a40, 0xc0001d2720})
/usr/local/go/src/net/http/server.go:1991 +0x607
created by net/http.(*Server).Serve
/usr/local/go/src/net/http/server.go:3102 +0x4db
I tried this on a couple of different machines (Linux and macOS) and both fail with this error:
go get github.com/go-chi/jwtauth
cannot find package "github.com/decred/dcrd/dcrec/secp256k1/v3" in any of:
/usr/local/Cellar/go/1.15.8/libexec/src/github.com/decred/dcrd/dcrec/secp256k1/v3 (from $GOROOT)
/Users/saeedzab/go/src/github.com/decred/dcrd/dcrec/secp256k1/v3 (from $GOPATH)
Is this a known issue?
It's entirely possible I'm doing something wrong, but I can't see it. The parsed jwt data is there when I dump out the context with %+v
, but it's not coming back from ctx.Value
. Building and running this example, then hitting localhost:8080
in the browser will dump the context to stderr
with %+v
, and write the specific error to the browser.
package main
import (
"fmt"
"log"
"net/http"
"os"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/goware/jwtauth"
"github.com/pressly/chi"
)
var TokenAuth *jwtauth.JwtAuth
func init() {
TokenAuth = jwtauth.New("HS256", []byte("fooo"), nil)
}
func main() {
r := chi.NewRouter()
r.Get("/", Authenticate)
r.Group(func(r chi.Router) {
r.Use(TokenAuth.Verifier)
r.Use(jwtauth.Authenticator)
r.Route("/foo", func(r chi.Router) {
r.Get("/", FooIndex)
})
})
log.Fatal(http.ListenAndServe(":8080", r))
}
func Authenticate(w http.ResponseWriter, r *http.Request) {
cl := jwtauth.Claims{}.
SetExpiryIn(time.Hour*24).
SetIssuedNow().
Set("uid", 13)
_, str, err := TokenAuth.Encode(cl)
if err != nil {
http.Error(w, http.StatusText(500), 500)
}
c := &http.Cookie{Name: "jwt", Value: str}
http.SetCookie(w, c)
w.Header().Set("Location", "/foo")
w.WriteHeader(http.StatusFound)
}
func FooIndex(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
fmt.Fprintf(os.Stderr, "%+v\n", ctx)
token, ok := ctx.Value("jwt").(*jwt.Token)
if !ok {
fmt.Fprintln(os.Stderr, "no auth context available!")
http.Error(w, "no auth context available", 404)
return
} else if token == nil {
http.Error(w, "no token available", 404)
return
} else if !token.Valid {
http.Error(w, "invalid token", 403)
return
}
fmt.Fprintf(os.Stderr, "%+v\n", token)
// this cast & check is no longer necessary after vendoring jwt-go 2.7.0
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
http.Error(w, "can't cast claims", 422)
return
}
// claims => token.Claims after vendoring jwt-go 2.7.0
uid, ok := claims["uid"].(float64)
if !ok {
http.Error(w, "no user available", 404)
return
}
fmt.Fprintf(w, "user found (%d)", uid)
}
Currently, if the name of the query param or cookie is other than jwt
, it is necessary to implement logic to obtain jwt from the query or cookie on the user side(that's a bit cumbersome).
TokenFromCookie function
TokenFromQuery function
Solve the above problem by providing a function that can retrieve jwt from a query or cookie with the name passed as an argument.
google cloud builder is complaining for me when I use
Encode(jwt.MapClaims{"UserID": user.ID})
with the error
cannot use jwt.MapClaims literal (type jwt.MapClaims) as type jwtauth.Claims
looking here, https://github.com/go-chi/jwtauth/blob/master/jwtauth.go#L134 it seems like the Encode function is expecting jwt.Claims{} but based on example in the readme regarding token generation, that doesn't seem right
This is an issue with github.com/lestrrat-go
but I'm reporting it here because I suspect others upgrading v1.1.0 and above (and hence moving from github.com/dgrijalva/jwt-go
to github.com/lestrrat-go/jwx
) may encounter it (and it took me a while to trace the cause). If a token contains a null (e.g. "nullValue": null
) then calling jwtauth.Authenticator
will result in a panic.
panic(0xcd2ae0, 0xc00000d8f0)
runtime/panic.go:965 +0x1b9
reflect.Value.Type(0x0, 0x0, 0x0, 0x1256801, 0xca1940)
reflect/value.go:1908 +0x189
github.com/lestrrat-go/iter/mapiter.AsMap(0x124a168, 0xc000096000, 0xdbd8a0, 0xc000d508a0, 0xc84f80, 0xc00000e948, 0xdbd8a0, 0x1253048)
github.com/lestrrat-go/[email protected]/mapiter/mapiter.go:180 +0x5c5
github.com/lestrrat-go/jwx/internal/iter.AsMap(0x124a168, 0xc000096000, 0x123a520, 0xc000d508a0, 0xdbd8a0, 0xdbd801, 0x1253048)
github.com/lestrrat-go/[email protected]/internal/iter/mapiter.go:31 +0x86
github.com/lestrrat-go/jwx/jwt.(*stdToken).AsMap(0xc000d508a0, 0x124a168, 0xc000096000, 0x1253048, 0xc000d508a0, 0x1)
github.com/lestrrat-go/[email protected]/jwt/token_gen.go:491 +0x4b
github.com/go-chi/jwtauth/v5.FromContext(0x124a1d8, 0xc0011a1620, 0x7fa161cc75b8, 0x90, 0xc00053aa20, 0xb994de, 0xc000248c00)
github.com/go-chi/jwtauth/[email protected]/jwtauth.go:195 +0xb5
github.com/go-chi/jwtauth/v5.Authenticator.func1(0x1246738, 0xc0005fe460, 0xc000984700)
github.com/go-chi/jwtauth/[email protected]/jwtauth.go:165 +0x5d
This issue in the upstream project is: lestrrat-go/iter#1 (I have posted my work around there).
The following demonstrates how this can be replicated within github.com/go-chi/jwtauth:
package main
import (
"context"
"crypto/rand"
"crypto/rsa"
"fmt"
"net/http"
"github.com/go-chi/jwtauth"
"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jwk"
"github.com/lestrrat-go/jwx/jwt"
)
func main() {
// Generate a new private key so we can sign a test token
privKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
fmt.Printf("failed to generate private key: %s\n", err)
return
}
realKey, err := jwk.New(privKey)
if err != nil {
fmt.Printf("failed to create JWK: %s\n", err)
return
}
realKey.Set(jwk.KeyIDKey, `mykey`)
// Generate the test token with a nil value
token := jwt.New()
token.Set("nullValue", nil)
// Sign the token and generate a payload
signed, err := jwt.Sign(token, jwa.RS256, realKey)
if err != nil {
panic(fmt.Sprintf("failed to generate signed payload: %s\n", err))
}
fmt.Printf("token generated: %s\n", string(signed))
// Now we use jwtauth to process the token (simulating extraction from a cookie)
var r *http.Request // Will not actually be used..
tokenAuth := jwtauth.New("RS256", nil, privKey.Public())
token, err = jwtauth.VerifyRequest(tokenAuth, r, func(r *http.Request) string { return string(signed) })
if err != nil {
panic(fmt.Sprintf("Failed to process cookie: %s", err))
}
// Add the token to the context
ctx := jwtauth.NewContext(context.Background(), token, nil)
// Attempt to retrieve from the context (Authenticator makes this call)
jwtauth.FromContext(ctx) // panic here
}
vendor/github.com/go-chi/jwtauth/jwtauth.go:44: parser.SkipClaimsValidation undefined (type *jwt.Parser has no field or method SkipClaimsValidation)
Looks like SkipClaimsValidation is no longer part of the Parser struct in 3.0.0.
https://github.com/dgrijalva/jwt-go/blob/release_3_0_0/parser.go
Using dep to install dependencies it tries to pull in version 3.0.0 because that is what is listed in the Gopkg.toml file.
[[constraint]]
name = "github.com/dgrijalva/jwt-go"
version = "^3.0.0"
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.