Skip to content

Commit

Permalink
feat(oidc): idToken can be null on a refresh (release) (#1271)
Browse files Browse the repository at this point in the history
  • Loading branch information
guillaume-chervet committed Jan 29, 2024
1 parent 7749c5d commit 1676881
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,13 @@ class OidcConfigBuilder {
return this;
}

public withOidcConfiguration(
oidcConfiguration: OidcConfiguration,
): OidcConfigBuilder {
this.oidcConfig.oidcConfiguration = oidcConfiguration;
return this;
}

public build() {
return this.oidcConfig;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,5 +104,33 @@ describe('tokens', () => {
expect(secureTokens.access_token).toBe(expectedAccessToken);
expect(typeof secureTokens.expiresAt).toBe("number");
});

it('should reuse old id_token', () => {
const token = new TokenBuilder().withNonExpiredToken().build();
// @ts-ignore
delete token.id_token;
// @ts-ignore
delete token.idTokenPayload;
const oidcConfiguration = new OidcConfigBuilder()
.withOidcConfiguration({token_renew_mode: "access_token_invalid"})
.withOidcServerConfiguration({issuer: "",
authorizationEndpoint:"",
revocationEndpoint:"",
tokenEndpoint:"",
userInfoEndpoint:"" })
.withTokens(new TokenBuilder()
.withNonExpiredToken()
.withIdToken("old_id_token")
.withIdTokenPayload({
iss: oidcServerConfig.issuer,
exp: 0,
iat: 0,
nonce: null,
})
.build()).build();
const secureTokens = _hideTokens(token, oidcConfiguration, 'test');
expect(secureTokens.id_token).toBe("old_id_token");

});
});
});
20 changes: 15 additions & 5 deletions packages/oidc-client-service-worker/src/utils/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,19 @@ function _hideTokens(tokens: Tokens, currentDatabaseElement: OidcConfig, configu
}
tokens.accessTokenPayload = accessTokenPayload;

// When id_token is not rotated we reuse old id_token
const oldTokens = currentDatabaseElement.tokens;
let id_token: string | null;
if (oldTokens != null && 'id_token' in oldTokens && !('id_token' in tokens)) {
id_token = oldTokens.id_token;
} else {
id_token = tokens.id_token;
}
tokens.id_token = id_token;

let _idTokenPayload = null;
if (tokens.id_token) {
_idTokenPayload = extractTokenPayload(tokens.id_token);
if (id_token) {
_idTokenPayload = extractTokenPayload(id_token);
tokens.idTokenPayload = { ..._idTokenPayload };
if (_idTokenPayload.nonce && currentDatabaseElement.nonce != null) {
const keyNonce =
Expand Down Expand Up @@ -193,11 +203,11 @@ function _hideTokens(tokens: Tokens, currentDatabaseElement: OidcConfig, configu

// When refresh_token is not rotated we reuse ald refresh_token
if (
currentDatabaseElement.tokens != null &&
'refresh_token' in currentDatabaseElement.tokens &&
oldTokens != null &&
'refresh_token' in oldTokens &&
!('refresh_token' in tokens)
) {
const refreshToken = currentDatabaseElement.tokens.refresh_token;
const refreshToken = oldTokens.refresh_token;

currentDatabaseElement.tokens = {
...tokens,
Expand Down
78 changes: 77 additions & 1 deletion packages/oidc-client/src/parseTokens.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { describe, expect,it } from 'vitest';

import { getValidTokenAsync, isTokensOidcValid, parseJwt, parseOriginalTokens} from "./parseTokens";
import {
getValidTokenAsync,
isTokensOidcValid,
parseJwt,
parseOriginalTokens,
setTokens,
TokenRenewMode
} from "./parseTokens";

describe('ParseTokens test Suite', () => {
const currentTimeUnixSecond = new Date().getTime() / 1000;
Expand Down Expand Up @@ -94,5 +101,74 @@ describe('ParseTokens test Suite', () => {
expect(isValid).toEqual(expectIsValidToken);
});
});


const testTokens = {
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkMyNTJGOUNBQjc3Q0MxNTQwNTBFMTg1NTk5MjJCMTJGIiwidHlwIjoiSldUIn0.eyJpc3MiOiJodHRwczovL2RlbW8uZHVlbmRlc29mdHdhcmUuY29tIiwibmJmIjoxNzA2NTQwMjU4LCJpYXQiOjE3MDY1NDAyNTgsImV4cCI6MTcwNjU0MDU1OCwiYXVkIjoiaW50ZXJhY3RpdmUucHVibGljLnNob3J0IiwiYW1yIjpbInB3ZCJdLCJub25jZSI6IlA5dEo5eGxHZE05NiIsImF0X2hhc2giOiJOWnZhR0dZYlhoelRNWlVxUjlNYk5nIiwic2lkIjoiMzQ1QUJDODhFNkU1MEFGMTI3M0VENDE1QTdGRDZBMjMiLCJzdWIiOiIyIiwiYXV0aF90aW1lIjoxNzA2NTMxNjY1LCJpZHAiOiJsb2NhbCJ9.MVtXrCkshJFBplbOw7az3fdWB1Ewqixb2fuHXpx7KbGWUY6qgT9ijlldeD-ZV7JGA958AKqmGwfNjovAJE89pQsCFKkNft6fRO8eM9qKif6eRUqMMPiQrawARpuJOs1NvJ-SyeRs_jSNLwPVzI8NlZyFWHoyQ4DZnFoQLSQMy5UaHaCtWhC_FrWMFLQvbE3RuMlnJGzrsoMewFyVAZctMCTE1MOI3Akvhe1IGc1hmxzwNg3OkxwzHLinsDlDw8UVn8vX5iNI18GFuyTuJlawOq5OHHJH3LdKQD_RbwRF-9BFjKRZfWzGpdpxTD2lIPf1Irc3U_R6xCNuXYUwzrHp6Q",
"access_token": "ACCESS_TOKEN_SECURED_BY_OIDC_SERVICE_WORKER_default",
"expires_in": 75,
"token_type": "Bearer",
"refresh_token": "REFRESH_TOKEN_SECURED_BY_OIDC_SERVICE_WORKER_default",
"scope": "openid profile email api offline_access",
"issued_at": 1706540256.465,
"accessTokenPayload": {
"iss": "https://demo.duendesoftware.com",
"nbf": 1706540258,
"iat": 1706540258,
"exp": 1706540333,
"aud": "api",
"scope": [
"openid",
"profile",
"email",
"api",
"offline_access"
],
"amr": [
"pwd"
],
"client_id": "interactive.public.short",
"sub": "2",
"auth_time": 1706531665,
"idp": "local",
"name": "Bob Smith",
"email": "BobSmith@email.com",
"sid": "345ABC88E6E50AF1273ED415A7FD6A23",
"jti": "E3CF3853D77AC90ABC774266CD381C43"
},
"idTokenPayload": {
"iss": "https://demo.duendesoftware.com",
"nbf": 1706540258,
"iat": 1706540258,
"exp": 1706540558,
"aud": "interactive.public.short",
"amr": [
"pwd"
],
"nonce": "NONCE_SECURED_BY_OIDC_SERVICE_WORKER_default",
"at_hash": "NZvaGGYbXhzTMZUqR9MbNg",
"sid": "345ABC88E6E50AF1273ED415A7FD6A23",
"sub": "2",
"auth_time": 1706531665,
"idp": "local"
},
"expiresAt": 1706540333
}

describe.each([
[testTokens, null, TokenRenewMode.access_token_invalid, () => {}],
[testTokens, {testTokens, idTokenPayload: undefined, id_token: undefined}, TokenRenewMode.access_token_invalid, (newTokens:any) => {
expect(newTokens.idTokenPayload).toBeDefined();
expect(newTokens.id_token).toBeDefined();
}],
])('setTokens', (tokens, oldTokens, tokenRenewMode, validationFunction) => {
it('should setTokens return updatedTokens' , async () => {
const oidc = {
idTokenPayload,
};
const newTokens = setTokens(tokens, oldTokens, tokenRenewMode);
validationFunction(newTokens)
});
});

});
15 changes: 12 additions & 3 deletions packages/oidc-client/src/parseTokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,16 @@ export const setTokens = (tokens, oldTokens = null, tokenRenewMode: string):Toke
} else {
accessTokenPayload = extractTokenPayload(tokens.accessToken);
}
const _idTokenPayload = tokens.idTokenPayload ? tokens.idTokenPayload : extractTokenPayload(tokens.idToken);

// When id_token is not rotated we reuse old id_token
let idToken: string;
if (oldTokens != null && 'idToken' in oldTokens && !('idToken' in tokens)) {
idToken = oldTokens.idToken;
} else {
idToken = tokens.idToken;
}

const _idTokenPayload = tokens.idTokenPayload ? tokens.idTokenPayload : extractTokenPayload(idToken);

const idTokenExpireAt = (_idTokenPayload && _idTokenPayload.exp) ? _idTokenPayload.exp : Number.MAX_VALUE;
const accessTokenExpiresAt = (accessTokenPayload && accessTokenPayload.exp) ? accessTokenPayload.exp : tokens.issuedAt + expireIn;
Expand All @@ -96,8 +105,8 @@ export const setTokens = (tokens, oldTokens = null, tokenRenewMode: string):Toke
}
}

const newTokens = { ...tokens, idTokenPayload: _idTokenPayload, accessTokenPayload, expiresAt };
// When refresh_token is not rotated we reuse ald refresh_token
const newTokens = { ...tokens, idTokenPayload: _idTokenPayload, accessTokenPayload, expiresAt, idToken };
// When refresh_token is not rotated we reuse old refresh_token
if (oldTokens != null && 'refreshToken' in oldTokens && !('refreshToken' in tokens)) {
const refreshToken = oldTokens.refreshToken;
return { ...newTokens, refreshToken };
Expand Down

0 comments on commit 1676881

Please sign in to comment.