简体   繁体   中英

Validate state and nonce in oidc-client

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;
      });

状态未定义,但会话状态具有价值

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在此处输入图像描述

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?

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.

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);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM