You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I am attempting to implement a service that can generate test users and provide the access token / id token to the developer (or other automated service) so they can authenticate as that user. I wanted to try using JWT Profiles since I see them recommended in multiple discussions / issues; however, I wanted to check if my observations are correct that I'm not sure the correct hooks exists today in the library. I am happy to submit a PR to help implement this but wanted to make sure what path y'all wanted to take before going down this rabbit hole.
For comparison, the client credentials endpoint has the following function that allows you to set subject/audience/etc.
The JWT Profile on the other hand has ValidateJWTProfileScopes which only allows you to modify the provided scopes
// ValidateJWTProfileScopes implements the op.Storage interface// it will be called to validate the scopes of a JWT Profile Authorization Grant requestfunc (s*Storage) ValidateJWTProfileScopes(ctx context.Context, userIDstring, scopes []string) ([]string, error) {
allowedScopes:=make([]string, 0)
for_, scope:=rangescopes {
ifscope==oidc.ScopeOpenID {
allowedScopes=append(allowedScopes, scope)
}
}
returnallowedScopes, nil
}
The token response endpoint also seems to be missing the id token
// by default the access_token is an opaque string, but can be specified by implementing the JWTProfileTokenStorage interfacefuncCreateJWTTokenResponse(ctx context.Context, tokenRequestTokenRequest, creatorTokenCreator) (*oidc.AccessTokenResponse, error) {
ctx, span:=tracer.Start(ctx, "CreateJWTTokenResponse")
deferspan.End()
// return an opaque token as default to not break current implementationstokenType:=AccessTokenTypeBearer// the current CreateAccessToken function, esp. CreateJWT requires an implementation of an AccessTokenClientclient:=&jwtProfileClient{
id: tokenRequest.GetSubject(),
}
// by implementing the JWTProfileTokenStorage the storage can specify the AccessTokenType to be returnedtokenStorage, ok:=creator.Storage().(JWTProfileTokenStorage)
ifok {
varerrerrortokenType, err=tokenStorage.JWTProfileTokenType(ctx, tokenRequest)
iferr!=nil {
returnnil, err
}
}
accessToken, _, validity, err:=CreateAccessToken(ctx, tokenRequest, tokenType, creator, client, "")
iferr!=nil {
returnnil, err
}
return&oidc.AccessTokenResponse{
AccessToken: accessToken,
// id token missing hereTokenType: oidc.BearerToken,
ExpiresIn: uint64(validity.Seconds()),
}, nil
}
Client Limitations
Looking at the NewJWTProfileTokenSource it forces the audience to be the issuer (which in my case if I want to generate a token for my test user for a different service it won't match the issuer)
// NewJWTProfileSource returns an implementation of oauth2.TokenSource// It will request a token using the OAuth2 JWT Profile Grant,// therefore sending an `assertion` by singing a JWT with the provided private key.//// The passed context is only used for the call to the Discover endpoint.funcNewJWTProfileTokenSource(ctx context.Context, issuer, clientID, keyIDstring, key []byte, scopes []string, options...func(source*jwtProfileTokenSource)) (TokenSource, error) {
signer, err:=client.NewSignerFromPrivateKeyByte(key, keyID)
iferr!=nil {
returnnil, err
}
source:=&jwtProfileTokenSource{
clientID: clientID,
audience: []string{issuer},
signer: signer,
scopes: scopes,
httpClient: http.DefaultClient,
}
for_, opt:=rangeoptions {
opt(source)
}
ifsource.tokenEndpoint=="" {
config, err:=client.Discover(ctx, issuer, source.httpClient)
iferr!=nil {
returnnil, err
}
source.tokenEndpoint=config.TokenEndpoint
}
returnsource, nil
}
I attempted to change the audience to my service by using
source, err:=profile.NewJWTProfileTokenSource(ctx,
"my api client id",
"my test user id",
"jwt key id i generate for the test user",
"private key i generate for the test user",
[]strings{"my", "custom", "scopes"}, # seeminglyallgoodhereprofile.WithStaticTokenEndpoint("test", "http://localhost:8080/oauth/token"),
)
but the server rejects this with 'audience must contain client_id'
Describe your ideal solution
I want to have a machine service that is able to use the JWT Profile to generate access + id tokens for test users that is identical to what a user would get when logging through using other grant types. The machine service needs to be able to set the subject, audience, and scopes of the access token (and preferably also the userinfo that can get picked up via introspection) so that it can return it to the other user / integration test service
Expected Changes
Comparing the JWTProfile handler and ClientCredential handler they have very similar structures but ClientCredentials has validatedRequest, client, err := ValidateClientCredentialsRequest(r.Context(), request, exchanger) which can modify the request; however, JWTProfileScopes only has ValidateJWTProfileScopes(r.Context(), tokenRequest.Issuer, profileRequest.Scope) so it seems like changing this function over to a more generate ValidateJWTProfile would solve this particular problem
When you use the Provider / Storage interfaces the business logic is kind of locked and does not allow for your use-case. We implement the standards and your usecase is non-standard.
Instead, I will recommend using the Server interface. It will provide you with the request and you can handle it however you wish.
Preflight Checklist
Describe your problem
I am attempting to implement a service that can generate test users and provide the access token / id token to the developer (or other automated service) so they can authenticate as that user. I wanted to try using JWT Profiles since I see them recommended in multiple discussions / issues; however, I wanted to check if my observations are correct that I'm not sure the correct hooks exists today in the library. I am happy to submit a PR to help implement this but wanted to make sure what path y'all wanted to take before going down this rabbit hole.
For comparison, the client credentials endpoint has the following function that allows you to set subject/audience/etc.
Server Limitations
The JWT Profile on the other hand has
ValidateJWTProfileScopes
which only allows you to modify the provided scopesThe token response endpoint also seems to be missing the id token
Client Limitations
Looking at the
NewJWTProfileTokenSource
it forces the audience to be the issuer (which in my case if I want to generate a token for my test user for a different service it won't match the issuer)I attempted to change the audience to my service by using
but the server rejects this with 'audience must contain client_id'
Describe your ideal solution
I want to have a machine service that is able to use the JWT Profile to generate access + id tokens for test users that is identical to what a user would get when logging through using other grant types. The machine service needs to be able to set the subject, audience, and scopes of the access token (and preferably also the userinfo that can get picked up via introspection) so that it can return it to the other user / integration test service
Expected Changes
Comparing the JWTProfile handler and ClientCredential handler they have very similar structures but ClientCredentials has
validatedRequest, client, err := ValidateClientCredentialsRequest(r.Context(), request, exchanger)
which can modify the request; however, JWTProfileScopes only hasValidateJWTProfileScopes(r.Context(), tokenRequest.Issuer, profileRequest.Scope)
so it seems like changing this function over to a more generateValidateJWTProfile
would solve this particular problemThe client would then need to be modified to allow specifying different audiences which should be fairly trival.
Version
github.com/zitadel/oidc/v3 v3.30.1
Additional Context
No response
The text was updated successfully, but these errors were encountered: