3

what I understood is- oidc-client generates nonce and state and sends it to an authorization server(Identity server 4). This is used to prevent CSRF attack, replay attack.

State and nonce are sent through signinredirect() sample example below

https://auth.azurewebsites.net/Account/Login?
ReturnUrl=%2Fconnect%2Fauthorize%2Fcallback%3F
client_id%3DLocal%26
redirect_uri%3Dhttp%253A%252F%252Flocalhost%253A4200%252Fauth-callback%252F%26
response_type%3Did_token%2520token%26
scope%3Dopenid%2520profile%2520Api%26
state%3D212ee56661074896aea2b6043d2b8a3f%26
nonce%3D393838b342d543d5910f38cbcab22fa0%26
loginType%3DInternal // my extra params

Issue 1 - state is undefined after callback

State is added to callback URL as below

    http://localhost:4200/auth-callback#id_token=eyJhbG...
    token_type=Bearer
    &expires_in=300&
    scope=openid%20profile%20Api&
    state=155e3e4352814ca48e127547c134144e&
    session_state=DPXW-ijMR4ST9iTSxgMwhsLq7aoknEZOnq3aFDooCFg.ifImJurwkwU6M5lwZXCUuw

State must be present in user. But in my case, I see the state as undefined in the callback method

  async completeAuthentication() {
    await this.manager
      .signinRedirectCallback()
      .then(x => {
        this.user = x;
        this.user.state = x.state; // undefined
        this.user.session_state = x.session_state;
      })
      .catch(errorData => {
        const expired = errorData;
      });

state is undefined but session state has value

Question --

  1. Where does oidc store state after generation?
  2. Why is state undefined? How to retrieve state after callback? I guess not by URL(path)!
  3. Does oidc internally validates state? How? Where?

Issue 2 - nonce

nonce value is received in id_token

created: 1594171097
extraTokenParams: {}
id: "5cc732d3b7fe4a0abdb371be3bda69a6"
nonce: "17c3f171328b4542a282fcbdd43d6fe4"

Also I see there are 2-4 oidc user are stored in local storage after login. why so? They have same user info but different ID and nonce. I user clearstalestate() to these all are generated after each fresh login or refresh enter image description here

Questions -

  1. Why 2-4 user info is stored in local storage? which method generates the local storage user?
  2. The nonce value is per session or per user request?
  3. Where does the nonce value stored after generation?
  4. Does oidc validates nonce internally? Where? If not how should I do it?
Ishika Jain
  • 949
  • 2
  • 11
  • 23
  • From above response it looks good, as I am using oidc lib and see no issues. I faced similar issue when I was using iFrame.Could it be possible for you to debug the oidc-client lib code? I think the library is send the state param but it is getting ilost in between – Sohan Jul 08 '20 at 07:03
  • Actually this looks issue to me.... I just debugged again and i could not see state in response of user object. I will have to debug the library – Sohan Jul 08 '20 at 07:17
  • Also, where does nonce and state get stored? When we perform login - ( return this.manager.signinRedirect() ) I simply redirects to authserver page nothing is saved in storage or cookie. Do I need to configure some settings? – Ishika Jain Jul 08 '20 at 07:26
  • 1
    What is the version of oidc lib you are using. I think the state is stored in storage (may be local). If you look at latest oidc lib, it does not return state param.https://github.com/IdentityModel/oidc-client-js/wiki – Sohan Jul 08 '20 at 07:45
  • I am using 1.9.1 - oidc-client. "It does not return state param" - OK – Ishika Jain Jul 08 '20 at 08:22
  • "I think the state is stored in storage (may be local) " before the client gets redirected to login page? – Ishika Jain Jul 08 '20 at 08:25
  • Not sure... as i said you need to check that on your browser. I do not have setup right now. I will check on my side.Also I will have to look the library code – Sohan Jul 08 '20 at 08:44
  • I would also suggest to pull latest library of oidc-client – Sohan Jul 08 '20 at 08:46

2 Answers2

1

So I have debugged the code and found the questions for your answers,

  • The nonce value is per session or per user request?

    This should not be duplicated, so it is generated per request to mitigate the replay attacks

  • Where does the nonce value stored after generation?

    Stored in session storage

  • Does oidc validates nonce internally? Where? If not how should I do it?

    Yes it validates internally. You will have to look at the oidc-client js. I extracted some of code from there to get clear view,

      _validateIdToken(state, response) {
         if (!state.nonce) {
             Log.error("ResponseValidator._validateIdToken: No nonce on state");
             return Promise.reject(new Error("No nonce on state"));
         }
    
         let jwt = this._joseUtil.parseJwt(response.id_token);
         if (!jwt || !jwt.header || !jwt.payload) {
             Log.error("ResponseValidator._validateIdToken: Failed to parse id_token", jwt);
             return Promise.reject(new Error("Failed to parse id_token"));
         }
    
         if (state.nonce !== jwt.payload.nonce) {
             Log.error("ResponseValidator._validateIdToken: Invalid nonce in id_token");
             return Promise.reject(new Error("Invalid nonce in id_token"));
         }
    

    }

Now coming back to state param validation. It is no longer available in User object, instead it is validated before hand internally. Here is the code extract for that from oidc-client js

processSigninResponse(url, stateStore) {
    Log.debug("OidcClient.processSigninResponse");

    var response = new SigninResponse(url);

    if (!response.state) {
        Log.error("OidcClient.processSigninResponse: No state in response");
        return Promise.reject(new Error("No state in response"));
    }

    stateStore = stateStore || this._stateStore;

    return stateStore.remove(response.state).then(storedStateString => {
        if (!storedStateString) {
            Log.error("OidcClient.processSigninResponse: No matching state found in storage");
            throw new Error("No matching state found in storage");
        }

        let state = SigninState.fromStorageString(storedStateString);

        Log.debug("OidcClient.processSigninResponse: Received state from storage; validating response");
        return this._validator.validateSigninResponse(state, response);
    });
}

Both, state and nonce are managed by oidc-client library.

Sohan
  • 6,252
  • 5
  • 35
  • 56
  • "Where does the nonce value stored after generation?" I think nonce is not in session storage, it is stored in user information in local storage (ex oidc.eds...). But if it is per request it should be stored every time we call the API, but it is not! Also, if it is per request than where can I see the nonce in each request? – Ishika Jain Jul 08 '20 at 15:17
  • There is option in OIDC to configure the local storage or session storage. I can see my nonce value in session storage, I am using session storage. Also to check the request, you need to debug and check you autorization request every time you send to IDP As per above answer to your question, if you look at code, you should not worry about handling these. As library does it for you and it is clear from code :) I am using this library since it was 1.4 – Sohan Jul 09 '20 at 06:06
  • It just prevents authorization(login) request from replay attack not the other resource API. I was getting confused here. Thanks – Ishika Jain Jul 09 '20 at 15:45
-1

Might be helpful for Authorization Code flow + PKCE. Still the PR is pending for merge and release. Right now nonce is generated only for response_type=id_token.

If we are working on Authorization Code flow + PKCE, currently this lib expects nonce to be present in state and to be matched with nonce present in Id_token.

https://github.com/IdentityModel/oidc-client-js/pull/1121

Below are some lines of code from lib

if (state.nonce && !response.id_token) {
        _Log.Log.error("ResponseValidator._processSigninParams: Expecting id_token in response");
        return Promise.reject(new Error("No id_token in response"));
    }

    if (!state.nonce && response.id_token) {
        _Log.Log.error("ResponseValidator._processSigninParams: Not expecting id_token in response");
        return Promise.reject(new Error("Unexpected id_token in response"));
    }

And nonce generates only if it is implicit flow

var oidc = SigninRequest.isOidc(response_type);
var code = SigninRequest.isCode(response_type);
Sandeep M
  • 248
  • 3
  • 6