-
Notifications
You must be signed in to change notification settings - Fork 92
[auth] Decode token once #1266
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
[auth] Decode token once #1266
Conversation
cmds/core-service/main.go
Outdated
| } | ||
|
|
||
| ctx = context.WithValue(r.Context(), auth.CtxAuthKey{}, auth.CtxAuthValue{ | ||
| Claims: claims, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using the context to pass the subject to the logger, we may want to zap it in the logger directly like the httpmiddleware is doing it. Could you please evaluate this approach and propose a new version ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In 9cbfe67 I added the logger as an argument to the auth middleware to write the values there and pass the new (enriched) logger to the httpMiddleware. What do you think ?
9cbfe6f to
5860a2e
Compare
|
@mickmis can you please have a second look since it is security related. Thank you. |
mickmis
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see this PR intends to optimize the runtime of the DSS in avoiding decoding the token twice by passing it through the context.
The main challenge here is dealing with the different approaches of how the logging and the authorization are performed. The logging is made through an http middleware, while the authorization is by invoking the authorizer close to the endpoint handler.
This PR deals with that by moving part of the authorization in a middleware. This is fine as long as:
- the split makes sense, here on one side we validate the token validity w.r.t. the instance (signature, audience) and on the other side w.r.t. the request (scopes)
- those related logics are not scattered away in the codebase
As such I suggest here to:
- move
authMiddlewareto being a method ofAuthorizerinauthpackage and rename it to reflect better its function, e.g.TokenMiddleware:
func (a *Authorizer) TokenMiddleware(handler http.Handler) http.Handler
// in main.go:
handler = authorizer.TokenMiddleware(handler)- clarify that
Authorizeauthorizes the request knowing that the token was already validated before by the middleware - unexpose unecessary exposed functions and structs (this is BTW a hint towards related logic being scattered) such as
ExtractClaimsorClaimsorAcceptedAudiences
pkg/auth/auth.go
Outdated
| type CtxAuthKey struct{} | ||
| type CtxAuthValue struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: you are in package auth, so you may drop the Auth part from the variable names.
cmds/core-service/main.go
Outdated
|
|
||
| ctx = context.WithValue(ctx, logging.CtxAuthKey{}, logging.CtxAuthValue{ | ||
| Subject: claims.Subject, | ||
| ErrMsg: errMsg, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is propagating the error necessary? Remove if not.
Then the subject may be propagated with a single string
Also, nit, but for context key you may do:
type CtxKey string
ctx = context.WithValue(ctx, CtxKey("sub"), parsedSub)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Propagating the error allows us to log it when the sub can't be found:
When provided with a valid token, the log outputs the following:
2025-09-03T06:21:28.402Z info logging/http.go:95 GET / HTTP/1.1 {"address": ":8082", "req_dump": "", "req_sub": "uss1", "resp_dump": "404 page not found\n", "req_headers": {"Accept":["application/json"],"Authorization":["Bearer ...
When the token decoding fails, the log outputs the following:
info logging/http.go:95 GET / HTTP/1.1 {"address": ":8082", "req_dump": "", "resp_sub_err": "Access token validation failed: token is expired by 29h16m20s", "resp_dump": "404 page not found\n", "req_headers": {"Accept":["application/json"],"Authorization":["Bearer
Is this aligned with the expected behavior ? If not, I can remove it.
|
@mickmis Since we moved the decoding into auth, I ended up adding a |
b630258 to
06b3ae2
Compare
Remove duplicate decoding
mickmis
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments, most importantly the one about decodedClaims. But we are getting there :)
|
|
||
| acceptedAudiences map[string]bool | ||
|
|
||
| decodedClaims *middlewareResult |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not OK to pass this through the Authorizer struct, we will have race conditions: decodedClaims is scoped to the request, while the authorizer is scoped to the runtime. Pass this through the context instead, like for the sub. The context is made for stuff scoped to the request.
| type CtxAuthError struct{} | ||
| type CtxAuthSubject struct{} | ||
| type CtxKey string | ||
| type CtxAuthValue struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: rather than a custom struct, I'd suggest to flatten this and just use two different keys with a string value type CtxKey("sub") and CtxKey("sub_err_msg").
| if !ok { | ||
| authErrorMsg := r.Context().Value(CtxAuthError{}).(string) | ||
| logger = logger.With(zap.String("resp_sub_err", authErrorMsg)) | ||
| authResults := r.Context().Value(CtxKey("sub")).(CtxAuthValue) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This removes the type assertion (with the value, ok := var.(Type) pattern), meaning that a wrong type will cause this line to panic (which may also happen when there is no value at all).
This is acceptable when it is obvious that a wrong type is a programming error. Which in this current version of the PR is the case (sub should always be added by the token middleware), however if you do implement my previous nit about flattening the CtxAuthValue, this might not be the case anymore (where you would have either sub or sub_err_msg).
| claims: claims, | ||
| err: err, | ||
| } | ||
| res := a.Authorize(nil, test.req.WithContext(context.Background()), []api.AuthorizationOption{}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: use t.Context() instead
| keyClaims, err := a.ExtractClaims(r) | ||
| if err != nil { | ||
| return api.AuthorizationResult{Error: stacktrace.PropagateWithCode(err, dsserr.Unauthenticated, "Failed to extract claims from access token")} | ||
| if a.decodedClaims == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should come from the context, and not form the Authorizer, c.f. other comment.
| return api.AuthorizationResult{Error: stacktrace.NewErrorWithCode(dsserr.Unauthenticated, "Access token not found")} | ||
| } | ||
|
|
||
| if !a.acceptedAudiences[keyClaims.Audience] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, why not keeping the audience check here if we have the claims?
| return keyClaims, nil | ||
| } | ||
|
|
||
| func HasScope(scopes []string, requiredScope api.RequiredScope) bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason why this function has moved? This makes the diff harder to read.
This PR is a follow-up to #1263 and removes the duplicate decoding of the authentication token.
It decodes the token once in the authentication middleware and passes the result in the context.