perf(oidc): remove get user by ID from jwt profile grant (#8580)

# Which Problems Are Solved

Improve performance by removing a GetUserByID call. The call also
executed a Trigger on projections, which significantly impacted
concurrent requests.

# How the Problems Are Solved

Token creation needs information from the user, such as the resource
owner and access token type.

For client credentials this is solved in a single search. By getting the
user by username (`client_id`), the user details and secret were
obtained in a single query. After that verification and token creation
can proceed. For JWT profile it is a bit more complex. We didn't know
anything about the user until after JWT verification.
The verification did a query for the AuthN key and after that we did a
GetUserByID to get remaining details.

This change uses a joined query when the OIDC library calls the
`GetKeyByIDAndClientID` method on the token storage. The found user
details are set to the verifieer object and returned after verification
is completed.
It is safe because the `jwtProfileKeyStorage` is a single-use object as
a wrapper around `query.Queries`.
This way getting the public key and user details are obtained in a
single query.

# Additional Changes

- Correctly set the `client_id` field with machine's username.

# Additional Context

- Related to: https://github.com/zitadel/zitadel/issues/8352
This commit is contained in:
Tim Möhlmann
2024-09-11 12:04:09 +03:00
committed by GitHub
parent 3aba942162
commit 58a7eb1f26
7 changed files with 154 additions and 35 deletions

View File

@@ -9,7 +9,7 @@ import (
"github.com/zitadel/oidc/v3/pkg/op"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors"
)
@@ -56,25 +56,29 @@ func (s *Server) clientCredentialsAuth(ctx context.Context, clientID, clientSecr
s.command.MachineSecretCheckSucceeded(ctx, user.ID, user.ResourceOwner, updated)
return &clientCredentialsClient{
id: clientID,
user: user,
clientID: user.Username,
userID: user.ID,
resourceOwner: user.ResourceOwner,
tokenType: user.Machine.AccessTokenType,
}, nil
}
type clientCredentialsClient struct {
id string
user *query.User
clientID string
userID string
resourceOwner string
tokenType domain.OIDCTokenType
}
// AccessTokenType returns the AccessTokenType for the token to be created because of the client credentials request
// machine users currently only have opaque tokens ([op.AccessTokenTypeBearer])
func (c *clientCredentialsClient) AccessTokenType() op.AccessTokenType {
return accessTokenTypeToOIDC(c.user.Machine.AccessTokenType)
return accessTokenTypeToOIDC(c.tokenType)
}
// GetID returns the client_id (username of the machine user) for the token to be created because of the client credentials request
func (c *clientCredentialsClient) GetID() string {
return c.id
return c.clientID
}
// RedirectURIs returns nil as there are no redirect uris