Skip to content

Conversation

@MariemBaccari
Copy link
Contributor

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.

@MariemBaccari MariemBaccari marked this pull request as ready for review September 9, 2025 14:18
}

ctx = context.WithValue(r.Context(), auth.CtxAuthKey{}, auth.CtxAuthValue{
Claims: claims,
Copy link
Contributor

@barroco barroco Sep 15, 2025

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 ?

Copy link
Contributor Author

@MariemBaccari MariemBaccari Sep 17, 2025

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 ?

@MariemBaccari MariemBaccari marked this pull request as draft September 18, 2025 07:20
@MariemBaccari MariemBaccari marked this pull request as ready for review September 18, 2025 09:37
@barroco
Copy link
Contributor

barroco commented Oct 13, 2025

@mickmis can you please have a second look since it is security related. Thank you.

Copy link
Contributor

@mickmis mickmis left a 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 authMiddleware to being a method of Authorizer in auth package 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 Authorize authorizes 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 ExtractClaims or Claims or AcceptedAudiences

pkg/auth/auth.go Outdated
Comment on lines 186 to 187
type CtxAuthKey struct{}
type CtxAuthValue struct {
Copy link
Contributor

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.


ctx = context.WithValue(ctx, logging.CtxAuthKey{}, logging.CtxAuthValue{
Subject: claims.Subject,
ErrMsg: errMsg,
Copy link
Contributor

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)

Copy link
Contributor Author

@MariemBaccari MariemBaccari Oct 21, 2025

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.

@MariemBaccari
Copy link
Contributor Author

@mickmis Since we moved the decoding into auth, I ended up adding a decodedClaims field to the Authorizer instead of passing the token in the context as it seemed cleaner. What do you think ?

@MariemBaccari MariemBaccari requested a review from mickmis October 21, 2025 16:41
Copy link
Contributor

@mickmis mickmis left a 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
Copy link
Contributor

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 {
Copy link
Contributor

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)
Copy link
Contributor

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{})
Copy link
Contributor

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 {
Copy link
Contributor

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] {
Copy link
Contributor

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 {
Copy link
Contributor

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.

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