Skip to content

Commit 6ad1ef8

Browse files
authored
fix: set refresh token in the RetrieveIdentityIntentResponse (zitadel#11613)
# Which Problems Are Solved This PR adds support for storing and retrieving refresh tokens from external identity providers during the IDP intent flow. # How the Problems Are Solved * Updated `idp.proto` (v2 and v2beta): added an optional `refresh_token` field to `IDPOAuthAccessInformation`, which, in turn, is used in `RetrieveIdentityProviderIntentResponse` * Added the `IDPRefreshToken` field to the IDP intent `SucceededEvent` struct, and updated the corresponding constructor to set the refresh token * Added the `IDPRefreshToken` field to `IDPIntentWriteModel`, and updated `reduceOAuthSucceededEvent` to populate refresh token from events * Updated `tokensForSucceededIDPIntent` function to extract and encrypt the refresh token from IDP session, if set * Updated `idpOAuthTokensToPb` function to decrypt refresh token before returning to clients * Updated unit and integration tests # Additional Changes Updated the link to the JWT IDP docs linked in the Console # Additional Context - Closes zitadel#11047
1 parent a5a5b65 commit 6ad1ef8

13 files changed

Lines changed: 223 additions & 76 deletions

File tree

console/src/app/modules/providers/provider-jwt/provider-jwt.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ <h1>{{ 'IDP.CREATE.JWT.TITLE' | translate }}</h1>
2121
[configureProvider]="(justCreated$ | async) === ''"
2222
[configureTitle]="'DESCRIPTIONS.SETTINGS.IDPS.JWT.TITLE' | translate"
2323
[configureDescription]="'DESCRIPTIONS.SETTINGS.IDPS.JWT.DESCRIPTION' | translate"
24-
configureLink="https://zitadel.com/docs/guides/integrate/identity-providers/jwt-idp"
24+
configureLink="https://zitadel.com/docs/guides/integrate/identity-providers/jwt_idp"
2525
[autofillLink]="autofillLink$ | async"
2626
[activateLink]="activateLink$ | async"
2727
[expanded]="!!(expandWhatNow$ | async)"

internal/api/grpc/user/v2/integration_test/intent_test.go

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -425,8 +425,9 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
425425
IdpInformation: &user.IDPInformation{
426426
Access: &user.IDPInformation_Oauth{
427427
Oauth: &user.IDPOAuthAccessInformation{
428-
AccessToken: "accessToken",
429-
IdToken: gu.Ptr("idToken"),
428+
AccessToken: "accessToken",
429+
RefreshToken: gu.Ptr("refreshToken"),
430+
IdToken: gu.Ptr("idToken"),
430431
},
431432
},
432433
IdpId: oauthIdpID,
@@ -476,8 +477,9 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
476477
IdpInformation: &user.IDPInformation{
477478
Access: &user.IDPInformation_Oauth{
478479
Oauth: &user.IDPOAuthAccessInformation{
479-
AccessToken: "accessToken",
480-
IdToken: gu.Ptr("idToken"),
480+
AccessToken: "accessToken",
481+
RefreshToken: gu.Ptr("refreshToken"),
482+
IdToken: gu.Ptr("idToken"),
481483
},
482484
},
483485
IdpId: oauthIdpID,
@@ -543,8 +545,9 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
543545
IdpInformation: &user.IDPInformation{
544546
Access: &user.IDPInformation_Oauth{
545547
Oauth: &user.IDPOAuthAccessInformation{
546-
AccessToken: "accessToken",
547-
IdToken: gu.Ptr("idToken"),
548+
AccessToken: "accessToken",
549+
RefreshToken: gu.Ptr("refreshToken"),
550+
IdToken: gu.Ptr("idToken"),
548551
},
549552
},
550553
IdpId: azureIdpID,
@@ -601,8 +604,9 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
601604
IdpInformation: &user.IDPInformation{
602605
Access: &user.IDPInformation_Oauth{
603606
Oauth: &user.IDPOAuthAccessInformation{
604-
AccessToken: "accessToken",
605-
IdToken: gu.Ptr("idToken"),
607+
AccessToken: "accessToken",
608+
RefreshToken: gu.Ptr("refreshToken"),
609+
IdToken: gu.Ptr("idToken"),
606610
},
607611
},
608612
IdpId: azureIdpID,
@@ -657,8 +661,9 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
657661
IdpInformation: &user.IDPInformation{
658662
Access: &user.IDPInformation_Oauth{
659663
Oauth: &user.IDPOAuthAccessInformation{
660-
AccessToken: "accessToken",
661-
IdToken: gu.Ptr("idToken"),
664+
AccessToken: "accessToken",
665+
RefreshToken: gu.Ptr("refreshToken"),
666+
IdToken: gu.Ptr("idToken"),
662667
},
663668
},
664669
IdpId: oidcIdpID,
@@ -707,8 +712,9 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
707712
IdpInformation: &user.IDPInformation{
708713
Access: &user.IDPInformation_Oauth{
709714
Oauth: &user.IDPOAuthAccessInformation{
710-
AccessToken: "accessToken",
711-
IdToken: gu.Ptr("idToken"),
715+
AccessToken: "accessToken",
716+
RefreshToken: gu.Ptr("refreshToken"),
717+
IdToken: gu.Ptr("idToken"),
712718
},
713719
},
714720
IdpId: oidcIdpID,

internal/api/grpc/user/v2/intent.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ func idpIntentToIDPIntentPb(intent *command.IDPIntentWriteModel, alg crypto.Encr
250250
information.Details = intentToDetailsPb(intent)
251251
// OAuth / OIDC
252252
if intent.IDPIDToken != "" || intent.IDPAccessToken != nil {
253-
information.IdpInformation.Access, err = idpOAuthTokensToPb(intent.IDPIDToken, intent.IDPAccessToken, alg)
253+
information.IdpInformation.Access, err = idpOAuthTokensToPb(intent.IDPIDToken, intent.IDPAccessToken, intent.IDPRefreshToken, alg)
254254
if err != nil {
255255
return nil, err
256256
}
@@ -274,7 +274,7 @@ func idpIntentToIDPIntentPb(intent *command.IDPIntentWriteModel, alg crypto.Encr
274274
return information, nil
275275
}
276276

277-
func idpOAuthTokensToPb(idpIDToken string, idpAccessToken *crypto.CryptoValue, alg crypto.EncryptionAlgorithm) (_ *user.IDPInformation_Oauth, err error) {
277+
func idpOAuthTokensToPb(idpIDToken string, idpAccessToken, idpRefreshToken *crypto.CryptoValue, alg crypto.EncryptionAlgorithm) (_ *user.IDPInformation_Oauth, err error) {
278278
var idToken *string
279279
if idpIDToken != "" {
280280
idToken = &idpIDToken
@@ -286,10 +286,19 @@ func idpOAuthTokensToPb(idpIDToken string, idpAccessToken *crypto.CryptoValue, a
286286
return nil, err
287287
}
288288
}
289+
var refreshToken *string
290+
if idpRefreshToken != nil {
291+
decryptedRefreshToken, err := crypto.DecryptString(idpRefreshToken, alg)
292+
if err != nil {
293+
return nil, err
294+
}
295+
refreshToken = &decryptedRefreshToken
296+
}
289297
return &user.IDPInformation_Oauth{
290298
Oauth: &user.IDPOAuthAccessInformation{
291-
AccessToken: accessToken,
292-
IdToken: idToken,
299+
AccessToken: accessToken,
300+
RefreshToken: refreshToken,
301+
IdToken: idToken,
293302
},
294303
}, nil
295304
}

internal/api/grpc/user/v2beta/integration_test/user_test.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2299,8 +2299,9 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
22992299
IdpInformation: &user.IDPInformation{
23002300
Access: &user.IDPInformation_Oauth{
23012301
Oauth: &user.IDPOAuthAccessInformation{
2302-
AccessToken: "accessToken",
2303-
IdToken: gu.Ptr("idToken"),
2302+
AccessToken: "accessToken",
2303+
RefreshToken: gu.Ptr("refreshToken"),
2304+
IdToken: gu.Ptr("idToken"),
23042305
},
23052306
},
23062307
IdpId: oauthIdpID,
@@ -2339,8 +2340,9 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
23392340
IdpInformation: &user.IDPInformation{
23402341
Access: &user.IDPInformation_Oauth{
23412342
Oauth: &user.IDPOAuthAccessInformation{
2342-
AccessToken: "accessToken",
2343-
IdToken: gu.Ptr("idToken"),
2343+
AccessToken: "accessToken",
2344+
RefreshToken: gu.Ptr("refreshToken"),
2345+
IdToken: gu.Ptr("idToken"),
23442346
},
23452347
},
23462348
IdpId: oauthIdpID,
@@ -2401,8 +2403,9 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
24012403
IdpInformation: &user.IDPInformation{
24022404
Access: &user.IDPInformation_Oauth{
24032405
Oauth: &user.IDPOAuthAccessInformation{
2404-
AccessToken: "accessToken",
2405-
IdToken: gu.Ptr("idToken"),
2406+
AccessToken: "accessToken",
2407+
RefreshToken: gu.Ptr("refreshToken"),
2408+
IdToken: gu.Ptr("idToken"),
24062409
},
24072410
},
24082411
IdpId: oidcIdpID,
@@ -2439,8 +2442,9 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
24392442
IdpInformation: &user.IDPInformation{
24402443
Access: &user.IDPInformation_Oauth{
24412444
Oauth: &user.IDPOAuthAccessInformation{
2442-
AccessToken: "accessToken",
2443-
IdToken: gu.Ptr("idToken"),
2445+
AccessToken: "accessToken",
2446+
RefreshToken: gu.Ptr("refreshToken"),
2447+
IdToken: gu.Ptr("idToken"),
24442448
},
24452449
},
24462450
IdpId: oidcIdpID,

internal/api/grpc/user/v2beta/user.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,7 @@ func idpIntentToIDPIntentPb(intent *command.IDPIntentWriteModel, alg crypto.Encr
522522
UserId: intent.UserID,
523523
}
524524
if intent.IDPIDToken != "" || intent.IDPAccessToken != nil {
525-
information.IdpInformation.Access, err = idpOAuthTokensToPb(intent.IDPIDToken, intent.IDPAccessToken, alg)
525+
information.IdpInformation.Access, err = idpOAuthTokensToPb(intent.IDPIDToken, intent.IDPAccessToken, intent.IDPRefreshToken, alg)
526526
if err != nil {
527527
return nil, err
528528
}
@@ -547,7 +547,7 @@ func idpIntentToIDPIntentPb(intent *command.IDPIntentWriteModel, alg crypto.Encr
547547
return connect.NewResponse(information), nil
548548
}
549549

550-
func idpOAuthTokensToPb(idpIDToken string, idpAccessToken *crypto.CryptoValue, alg crypto.EncryptionAlgorithm) (_ *user.IDPInformation_Oauth, err error) {
550+
func idpOAuthTokensToPb(idpIDToken string, idpAccessToken, idpRefreshToken *crypto.CryptoValue, alg crypto.EncryptionAlgorithm) (_ *user.IDPInformation_Oauth, err error) {
551551
var idToken *string
552552
if idpIDToken != "" {
553553
idToken = &idpIDToken
@@ -559,10 +559,19 @@ func idpOAuthTokensToPb(idpIDToken string, idpAccessToken *crypto.CryptoValue, a
559559
return nil, err
560560
}
561561
}
562+
var refreshToken *string
563+
if idpRefreshToken != nil {
564+
decryptedRefreshToken, err := crypto.DecryptString(idpRefreshToken, alg)
565+
if err != nil {
566+
return nil, err
567+
}
568+
refreshToken = &decryptedRefreshToken
569+
}
562570
return &user.IDPInformation_Oauth{
563571
Oauth: &user.IDPOAuthAccessInformation{
564-
AccessToken: accessToken,
565-
IdToken: idToken,
572+
AccessToken: accessToken,
573+
RefreshToken: refreshToken,
574+
IdToken: idToken,
566575
},
567576
}, nil
568577
}

internal/command/idp_intent.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ func (c *Commands) SucceedIDPIntent(ctx context.Context, writeModel *IDPIntentWr
170170
if err != nil {
171171
return "", err
172172
}
173-
accessToken, idToken, err := tokensForSucceededIDPIntent(idpSession, c.idpConfigEncryption)
173+
accessToken, refreshToken, idToken, err := tokensForSucceededIDPIntent(idpSession, c.idpConfigEncryption)
174174
if err != nil {
175175
return "", err
176176
}
@@ -186,6 +186,7 @@ func (c *Commands) SucceedIDPIntent(ctx context.Context, writeModel *IDPIntentWr
186186
idpUser.GetPreferredUsername(),
187187
userID,
188188
accessToken,
189+
refreshToken,
189190
idToken,
190191
idpSession.ExpiresAt(),
191192
)
@@ -295,8 +296,9 @@ func (c *Commands) GetIntentWriteModel(ctx context.Context, id, resourceOwner st
295296
return writeModel, err
296297
}
297298

298-
// tokensForSucceededIDPIntent extracts the oidc.Tokens if available (and encrypts the access_token) for the succeeded event payload
299-
func tokensForSucceededIDPIntent(session idp.Session, encryptionAlg crypto.EncryptionAlgorithm) (*crypto.CryptoValue, string, error) {
299+
// tokensForSucceededIDPIntent extracts the oidc.Tokens if available for the succeeded event payload.
300+
// It also encrypts the access token and refresh token (if set).
301+
func tokensForSucceededIDPIntent(session idp.Session, encryptionAlg crypto.EncryptionAlgorithm) (*crypto.CryptoValue, *crypto.CryptoValue, string, error) {
300302
var tokens *oidc.Tokens[*oidc.IDTokenClaims]
301303
switch s := session.(type) {
302304
case *oauth.Session:
@@ -312,11 +314,25 @@ func tokensForSucceededIDPIntent(session idp.Session, encryptionAlg crypto.Encry
312314
case *apple.Session:
313315
tokens = s.Tokens
314316
default:
315-
return nil, "", nil
317+
return nil, nil, "", nil
318+
}
319+
if tokens == nil {
320+
return nil, nil, "", nil
316321
}
317322
if tokens.Token == nil || tokens.AccessToken == "" {
318-
return nil, tokens.IDToken, nil
323+
return nil, nil, tokens.IDToken, nil
319324
}
320325
accessToken, err := crypto.Encrypt([]byte(tokens.AccessToken), encryptionAlg)
321-
return accessToken, tokens.IDToken, err
326+
if err != nil {
327+
return nil, nil, tokens.IDToken, err
328+
}
329+
var refreshToken *crypto.CryptoValue
330+
if tokens.RefreshToken != "" {
331+
refreshToken, err = crypto.Encrypt([]byte(tokens.RefreshToken), encryptionAlg)
332+
if err != nil {
333+
return nil, nil, tokens.IDToken, err
334+
}
335+
}
336+
337+
return accessToken, refreshToken, tokens.IDToken, nil
322338
}

internal/command/idp_intent_model.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ type IDPIntentWriteModel struct {
2323
UserID string
2424
LoginHint string
2525

26-
IDPAccessToken *crypto.CryptoValue
27-
IDPIDToken string
26+
IDPAccessToken *crypto.CryptoValue
27+
IDPRefreshToken *crypto.CryptoValue
28+
IDPIDToken string
2829

2930
IDPEntryAttributes map[string][]string
3031

@@ -131,6 +132,7 @@ func (wm *IDPIntentWriteModel) reduceOAuthSucceededEvent(e *idpintent.SucceededE
131132
wm.IDPUserID = e.IDPUserID
132133
wm.IDPUserName = e.IDPUserName
133134
wm.IDPAccessToken = e.IDPAccessToken
135+
wm.IDPRefreshToken = e.IDPRefreshToken
134136
wm.IDPIDToken = e.IDPIDToken
135137
wm.State = domain.IDPIntentStateSucceeded
136138
wm.succeededAt = e.CreationDate()

0 commit comments

Comments
 (0)