Skip to content

Add custom jwt extractor to jwt config#1967

Merged
aldas merged 1 commit intolabstack:masterfrom
RashadAnsari:master
Dec 20, 2021
Merged

Add custom jwt extractor to jwt config#1967
aldas merged 1 commit intolabstack:masterfrom
RashadAnsari:master

Conversation

@RashadAnsari
Copy link
Contributor

@RashadAnsari RashadAnsari commented Aug 19, 2021

This PR help users to extract their JWT tokens in which way they want.

@codecov
Copy link

codecov bot commented Aug 19, 2021

Codecov Report

Merging #1967 (b6bb420) into master (7d41537) will increase coverage by 0.30%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1967      +/-   ##
==========================================
+ Coverage   91.03%   91.33%   +0.30%     
==========================================
  Files          32       33       +1     
  Lines        2800     2875      +75     
==========================================
+ Hits         2549     2626      +77     
+ Misses        160      159       -1     
+ Partials       91       90       -1     
Impacted Files Coverage Δ
middleware/jwt.go 90.26% <100.00%> (ø)
middleware/compress.go 84.00% <0.00%> (-0.32%) ⬇️
echo.go 94.15% <0.00%> (ø)
middleware/timeout.go 100.00% <0.00%> (ø)
middleware/rate_limiter.go 100.00% <0.00%> (ø)
middleware/request_logger.go 97.59% <0.00%> (ø)
router.go 95.76% <0.00%> (+0.07%) ⬆️
middleware/request_id.go 85.71% <0.00%> (+1.50%) ⬆️
middleware/decompress.go 96.29% <0.00%> (+7.92%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d793521...b6bb420. Read the comment docs.

@RashadAnsari
Copy link
Contributor Author

@aldas Could you please review these changes?

@RashadAnsari
Copy link
Contributor Author

Does no one want to review this PR?

@aldas
Copy link
Contributor

aldas commented Dec 18, 2021

I have seen this but I am not very keen to accept it. I do not know the use-case. Currently JWT middleware allows to extract query, param, cookie, form, header values. I do not understand is there case these 5 variants does no cover?

Note: absolute minimal custom JWT middleware would be and extractor part can be whatever needed

e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		authHeader := c.Request().Header.Get(echo.HeaderAuthorization)
		rawToken := strings.TrimPrefix(authHeader, "Bearer ")
		if len(authHeader) == len(rawToken) {
			return middleware.ErrJWTMissing
		}
		jwtToken, err := jwt.Parse(rawToken, func(t *jwt.Token) (interface{}, error) {
			if t.Method.Alg() != "HS256" {
				return nil, fmt.Errorf("unexpected jwt signing method=%v", t.Header["alg"])
			}
			return "MySuperSecretSigningKey", nil
		})
		if err != nil {
			return err
		}
		if !jwtToken.Valid {
			return errors.New("invalid token")
		}
		c.Set("user", jwtToken)
		return next(c)
	}
})

@RashadAnsari
Copy link
Contributor Author

I have seen this but I am not very keen to accept it. I do not know the use-case. Currently JWT middleware allows to extract query, param, cookie, form, header values. I do not understand is there case these 5 variants does no cover?

Note: absolute minimal custom JWT middleware would be and extractor part can be whatever needed

e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		authHeader := c.Request().Header.Get(echo.HeaderAuthorization)
		rawToken := strings.TrimPrefix(authHeader, "Bearer ")
		if len(authHeader) == len(rawToken) {
			return middleware.ErrJWTMissing
		}
		jwtToken, err := jwt.Parse(rawToken, func(t *jwt.Token) (interface{}, error) {
			if t.Method.Alg() != "HS256" {
				return nil, fmt.Errorf("unexpected jwt signing method=%v", t.Header["alg"])
			}
			return "MySuperSecretSigningKey", nil
		})
		if err != nil {
			return err
		}
		if !jwtToken.Valid {
			return errors.New("invalid token")
		}
		c.Set("user", jwtToken)
		return next(c)
	}
})

If you see the test case I wrote, you will understand why I made this change. The standard header extractor does not handle this case because the auth scheme always is Bearer but, if you look at the tests with a custom extractor, you can implement whatever you want.
Actually, the default values that you assumed for some variables like auth scheme will make a problem for me.

@aldas
Copy link
Contributor

aldas commented Dec 19, 2021

Still. This seems to be shortcoming of JWTConfig.TokenLookup and JWTConfig.AuthScheme as usecase where token needs to be extracted from header does not work properly.

Ala it should be possible to:

	e.Use(JWTWithConfig(JWTConfig{
		TokenLookup: "header:X-API-Key",
		AuthScheme:  "", // maybe force empty somehow
		SigningKey:  []byte("secret"),
	}))

@RashadAnsari
Copy link
Contributor Author

Still. This seems to be shortcoming of JWTConfig.TokenLookup and JWTConfig.AuthScheme as usecase where token needs to be extracted from header does not work properly.

Ala it should be possible to:

	e.Use(JWTWithConfig(JWTConfig{
		TokenLookup: "header:X-API-Key",
		AuthScheme:  "", // maybe force empty somehow
		SigningKey:  []byte("secret"),
	}))

This doesn't work because for initializing the middleware, we have this condition:

if config.AuthScheme == "" {
    config.AuthScheme = DefaultJWTConfig.AuthScheme
}

@aldas
Copy link
Contributor

aldas commented Dec 19, 2021

I think we can modify "header" to support custom authscheme for each header with these changes

case "header":

		case "header":
			prefix := config.AuthScheme
			if len(parts) > 2 {
				prefix = parts[2]
			}
			extractors = append(extractors, jwtFromHeader(parts[1], prefix))

l := len(authScheme)

		l := len(authScheme)
		if l == 0 {
			return auth, nil
		}
		if len(auth) > l+1 && strings.EqualFold(auth[:l], authScheme) {

With this we can have

	e.Use(JWTWithConfig(JWTConfig{
		TokenLookup: "header:X-API-Key:", // auth scheme will be empty
		SigningKey:  []byte("secret"),
	}))

	e.Use(JWTWithConfig(JWTConfig{
		TokenLookup: "header:X-API-Key:MyAuth", // auth cheme will be "MyAuth"
		SigningKey:  []byte("secret"),
	}))

	e.Use(JWTWithConfig(JWTConfig{
		TokenLookup: "header:X-API-Key", // auth scheme will be "Bearer"
		SigningKey:  []byte("secret"),
	}))

Note: this would align with v5 changes - as in v5 JWTConfig.AuthScheme is dropped

@RashadAnsari
Copy link
Contributor Author

Actually, for me is better to make sense to add the ability to extract the token by a custom extractor. In this case, anyone can quickly write his own extractor if he needs and supports almost all varieties of custom use cases.
This is just an idea but, I think your proposed solution makes it a little complex and unreadable.

@aldas
Copy link
Contributor

aldas commented Dec 19, 2021

Fair enough, but change it so that:

  1. nameJWTExtractors to TokenLookupFuncs - lets call things with same names. We already have TokenLookup and other stuff has "*Func" prefixes.
  2. TokenLookup default value and TokenLookupFuncs are mutually exclusive - when you provide your own extractor funcs TokenLookup will not default to "header:Authorization". Atm extractors take the priority but that "Authorization: bearer xxxx" is also there at the end, added by the defaults.
  3. And add note to fields that you can mix TokenLookup (when you explicitly provide something) with funcs and funcs are executed first

@RashadAnsari
Copy link
Contributor Author

Great, I will make the changes.

@RashadAnsari
Copy link
Contributor Author

@aldas, Are you ok with the changes that I made?

Copy link
Contributor

@aldas aldas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@RashadAnsari
Copy link
Contributor Author

@aldas, Thank you for your approval.
Do you know when this PR will get merged?

@aldas aldas merged commit 4fffee2 into labstack:master Dec 20, 2021
@aldas
Copy link
Contributor

aldas commented Dec 20, 2021

done, there will be patch release probably in couple of days.

@RashadAnsari
Copy link
Contributor Author

Great, thank you so much.

@aldas
Copy link
Contributor

aldas commented Feb 2, 2022 via email

@xanderflood
Copy link

Yes! Right after I posted I looked at the source code and realized that was an option, so I deleted my question. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants