@axa-fr/react-oidc 6.3.0 → 6.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19,7 +19,7 @@ import timer from './timer';
19
19
  import {CheckSessionIFrame} from "./checkSessionIFrame"
20
20
  import {getParseQueryStringFromLocation} from "./route-utils";
21
21
  import {AuthorizationServiceConfigurationJson} from "@openid/appauth/src/authorization_service_configuration";
22
- import {computeTimeLeft, isTokensValid, parseOriginalTokens, setTokens} from "./parseTokens";
22
+ import {computeTimeLeft, isTokensOidcValid, isTokensValid, parseOriginalTokens, setTokens} from "./parseTokens";
23
23
 
24
24
  const performTokenRequestAsync= async (url, details, extras) => {
25
25
 
@@ -80,10 +80,12 @@ const internalFetch = async (url, headers, numberRetry=0) => {
80
80
 
81
81
  export interface OidcAuthorizationServiceConfigurationJson extends AuthorizationServiceConfigurationJson{
82
82
  check_session_iframe?: string;
83
+ issuer:string;
83
84
  }
84
85
 
85
86
  export class OidcAuthorizationServiceConfiguration extends AuthorizationServiceConfiguration{
86
87
  private check_session_iframe: string;
88
+ private issuer: string;
87
89
 
88
90
  constructor(request: any) {
89
91
  super(request);
@@ -92,6 +94,7 @@ export class OidcAuthorizationServiceConfiguration extends AuthorizationServiceC
92
94
  this.revocationEndpoint = request.revocation_endpoint;
93
95
  this.userInfoEndpoint = request.userinfo_endpoint;
94
96
  this.check_session_iframe = request.check_session_iframe;
97
+ this.issuer = request.issuer;
95
98
  }
96
99
 
97
100
  }
@@ -113,6 +116,7 @@ export interface AuthorityConfiguration {
113
116
  end_session_endpoint?: string;
114
117
  userinfo_endpoint?: string;
115
118
  check_session_iframe?:string;
119
+ issuer:string;
116
120
  }
117
121
 
118
122
  export type OidcConfiguration = {
@@ -519,6 +523,7 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
519
523
  token_endpoint: authorityConfiguration.token_endpoint,
520
524
  userinfo_endpoint: authorityConfiguration.userinfo_endpoint,
521
525
  check_session_iframe: authorityConfiguration.check_session_iframe,
526
+ issuer: authorityConfiguration.issuer,
522
527
  });
523
528
  }
524
529
 
@@ -636,23 +641,38 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
636
641
  scope = configuration.scope;
637
642
  }
638
643
 
644
+ const randomString = function(length) {
645
+ let text = "";
646
+ const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
647
+ for(let i = 0; i < length; i++) {
648
+ text += possible.charAt(Math.floor(Math.random() * possible.length));
649
+ }
650
+ return text;
651
+ }
652
+
639
653
  setLoginParams(this.configurationName, redirectUri, {callbackPath: url, extras, state});
640
-
654
+ const extraFinal = extras ?? configuration.extras ?? {};
655
+ if(!extraFinal.nonce) {
656
+ extraFinal["nonce"] = randomString(12);
657
+ }
658
+ const nonce = {"nonce":extraFinal.nonce};
641
659
  let serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
642
660
  const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
643
661
  let storage;
644
662
  if (serviceWorker) {
645
663
  serviceWorker.startKeepAliveServiceWorker();
646
664
  await serviceWorker.initAsync(oidcServerConfiguration, "loginAsync");
665
+ await serviceWorker.setNonceAsync(nonce);
647
666
  storage = new MemoryStorageBackend(serviceWorker.saveItemsAsync, {});
648
667
  await storage.setItem("dummy", {});
668
+
649
669
  } else {
650
670
  const session = initSession(this.configurationName, redirectUri);
671
+ await session.setNonceAsync(nonce);
651
672
  storage = new MemoryStorageBackend(session.saveItemsAsync, {});
652
673
  }
653
-
654
- const extraFinal = extras ?? configuration.extras ?? {};
655
-
674
+
675
+
656
676
  // @ts-ignore
657
677
  const queryStringUtil = redirectUri.includes("#") ? new HashQueryStringUtils() : new NoHashQueryStringUtils();
658
678
  const authorizationHandler = new RedirectRequestHandler(storage, queryStringUtil, window.location, new DefaultCrypto());
@@ -774,6 +794,7 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
774
794
  const sessionState = queryParams.session_state;
775
795
  const serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
776
796
  let storage = null;
797
+ let nonceData = null;
777
798
  if(serviceWorker){
778
799
  serviceWorker.startKeepAliveServiceWorker();
779
800
  this.serviceWorker = serviceWorker;
@@ -786,14 +807,16 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
786
807
  }
787
808
  await storage.removeItem("dummy");
788
809
  await serviceWorker.setSessionStateAsync(sessionState);
810
+ nonceData = await serviceWorker.getNonceAsync();
789
811
  }else{
790
-
791
812
  this.session = initSession(this.configurationName, redirectUri, configuration.storage ?? sessionStorage);
792
813
  const session = initSession(this.configurationName, redirectUri);
793
814
  session.setSessionState(sessionState);
794
815
  const items = await session.loadItemsAsync();
795
816
  storage = new MemoryStorageBackend(session.saveItemsAsync, items);
817
+ nonceData = await session.getNonceAsync();
796
818
  }
819
+
797
820
  return new Promise((resolve, reject) => {
798
821
  // @ts-ignore
799
822
  let queryStringUtil = new NoHashQueryStringUtils();
@@ -853,6 +876,16 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
853
876
  if (serviceWorker) {
854
877
  const {tokens} = await serviceWorker.initAsync(oidcServerConfiguration, "syncTokensAsync");
855
878
  tokenResponse = tokens;
879
+ };
880
+ if(!isTokensOidcValid(tokenResponse, nonceData.nonce, oidcServerConfiguration)){
881
+ const exception = new Error("Tokens are not OpenID valid");
882
+ if(timeoutId) {
883
+ clearTimeout(timeoutId);
884
+ this.timeoutId=null;
885
+ this.publishEvent(eventNames.loginCallbackAsync_error, exception);
886
+ console.error(exception);
887
+ reject(exception);
888
+ }
856
889
  }
857
890
 
858
891
  // @ts-ignore
@@ -971,6 +1004,10 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
971
1004
  const oidcServerConfiguration = await this.initAsync(authority, configuration.authority_configuration);
972
1005
  const tokenResponse = await performTokenRequestAsync(oidcServerConfiguration.tokenEndpoint, details, extras)
973
1006
  if (tokenResponse.success) {
1007
+ if(!isTokensOidcValid(tokenResponse.data, null, oidcServerConfiguration)){
1008
+ this.publishEvent(eventNames.refreshTokensAsync_error, {message: `refresh token return not valid tokens` });
1009
+ return {tokens:null, status:"SESSION_LOST"};
1010
+ }
974
1011
  this.publishEvent(eventNames.refreshTokensAsync_end, {success: tokenResponse.success});
975
1012
  this.publishEvent(Oidc.eventNames.token_renewed, {});
976
1013
  return {tokens: tokenResponse.data, status:"LOGGED_IN"};
@@ -51,7 +51,7 @@ export const setTokens = (tokens) =>{
51
51
  else {
52
52
  accessTokenPayload = extractAccessTokenPayload(tokens);
53
53
  }
54
- const _idTokenPayload = idTokenPayload(tokens.idToken);
54
+ const _idTokenPayload = tokens.idTokenPayload ? tokens.idTokenPayload : idTokenPayload(tokens.idToken);
55
55
 
56
56
  const idTokenExipreAt =(_idTokenPayload && _idTokenPayload.exp) ? _idTokenPayload.exp: Number.MAX_VALUE;
57
57
  const accessTokenExpiresAt = (accessTokenPayload && accessTokenPayload.exp)? accessTokenPayload.exp : tokens.issuedAt + tokens.expiresIn;
@@ -61,6 +61,7 @@ export const setTokens = (tokens) =>{
61
61
  }
62
62
 
63
63
 
64
+
64
65
  export const parseOriginalTokens= (tokens) =>{
65
66
  if(!tokens){
66
67
  return null;
@@ -104,4 +105,35 @@ export const isTokensValid= (tokens) =>{
104
105
  return false;
105
106
  }
106
107
  return computeTimeLeft(0, tokens.expiresAt) > 0;
108
+ }
109
+
110
+ // https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation (excluding rules #1, #4, #5, #7, #8, #12, and #13 which did not apply).
111
+ // https://github.com/openid/AppAuth-JS/issues/65
112
+ export const isTokensOidcValid =(tokens, nonce, oidcServerConfiguration) =>{
113
+ if(tokens.idTokenPayload) {
114
+ const idTokenPayload = tokens.idTokenPayload;
115
+ // 2: The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery) MUST exactly match the value of the iss (issuer) Claim.
116
+ if(oidcServerConfiguration.issuer !== idTokenPayload.iss){
117
+ return false;
118
+ }
119
+ // 3: The Client MUST validate that the aud (audience) Claim contains its client_id value registered at the Issuer identified by the iss (issuer) Claim as an audience. The aud (audience) Claim MAY contain an array with more than one element. The ID Token MUST be rejected if the ID Token does not list the Client as a valid audience, or if it contains additional audiences not trusted by the Client.
120
+
121
+ // 6: If the ID Token is received via direct communication between the Client and the Token Endpoint (which it is in this flow), the TLS server validation MAY be used to validate the issuer in place of checking the token signature. The Client MUST validate the signature of all other ID Tokens according to JWS [JWS] using the algorithm specified in the JWT alg Header Parameter. The Client MUST use the keys provided by the Issuer.
122
+
123
+ // 9: The current time MUST be before the time represented by the exp Claim.
124
+ const currentTimeUnixSecond = new Date().getTime() /1000;
125
+ if(idTokenPayload.exp && idTokenPayload.exp < currentTimeUnixSecond) {
126
+ return false;
127
+ }
128
+ // 10: The iat Claim can be used to reject tokens that were issued too far away from the current time, limiting the amount of time that nonces need to be stored to prevent attacks. The acceptable range is Client specific.
129
+ const timeInSevenDays = 60 * 60 * 24 * 7;
130
+ if(idTokenPayload.iat && (idTokenPayload.iat + timeInSevenDays) < currentTimeUnixSecond) {
131
+ return false;
132
+ }
133
+ // 11: If a nonce value was sent in the Authentication Request, a nonce Claim MUST be present and its value checked to verify that it is the same value as the one that was sent in the Authentication Request. The Client SHOULD check the nonce value for replay attacks. The precise method for detecting replay attacks is Client specific.
134
+ if (idTokenPayload.nonce && idTokenPayload.nonce !== nonce) {
135
+ return false;
136
+ }
137
+ }
138
+ return true;
107
139
  }