2

I am trying to get ReactJS working with IdentityServer4 via oidc-client-js library.

PROBLEM

I click the login button and I get redirected to IdentityServer4, once I login, I get redirect to /login-callback and then from there I get redirected to / but I get the following error in the console:

reactjs error no state in response

I am not sure what I'm doing wrong, but I've tried all sorts and nothing seems to work.

CODE

All code is open-source and sits here.

App.jsx

// other code ommited, but this is a route
<AuthHandshake path="/login-callback" />

AuthHandshake.jsx

import React from "react";
import { AuthConsumer } from "../AuthProvider/AuthProvider";

function AuthHandshake() {
  return <AuthConsumer>{value => value.loginCallback()}</AuthConsumer>;
}

export default AuthHandshake;

AuthProvider.jsx

import React, { useState } from "react";
import { navigate } from "@reach/router";
import { UserManager, WebStorageStateStore } from "oidc-client";
import AuthContext from "../../contexts/AuthContext";
import { IDENTITY_CONFIG } from "../../utils/authConfig";

IDENTITY_CONFIG.userStore = new WebStorageStateStore({
  store: window.localStorage
});

const userManager = new UserManager(IDENTITY_CONFIG);

const login = () => {
  console.log("Login button click handled.");
  userManager.signinRedirect();
};

const logout = () => {
  userManager.signoutRedirect();
};

export function AuthProvider(props) {
  const [user, setUser] = useState(null);

  const loginCallback = () => {
    userManager.signinRedirectCallback().then(
      user => {
        window.history.replaceState(
          {},
          window.document.title,
          window.location.origin
        );
        setUser(user);
        navigate("/");
      },
      error => {
        console.error(error);
      }
    );
  };

  const providerValue = {
    login: login,
    logout: logout,
    loginCallback: loginCallback,
    isAuthenticated: user
  };

  const Provider = () => {
    if (user) {
      return (
        <AuthContext.Provider value={providerValue}>
          {props.children}
        </AuthContext.Provider>
      );
    } else {
      return <div className="auth-provider">{props.children}</div>;
    }
  };

  return (
    <>
      <AuthContext.Provider value={providerValue}>
        {props.children}
      </AuthContext.Provider>
    </>
  );
}

export const AuthConsumer = AuthContext.Consumer;

On the IdentityServer side, I have set the post logout redirect to the same thing /login-callback

new Client
{
  ClientId = "bejebeje-react-local",
  ClientName = "Bejebeje ReactJS SPA Client",
  AllowedGrantTypes = GrantTypes.Code,
  RequirePkce = true,
  RequireClientSecret = false,
  RequireConsent = false,
  RedirectUris = { "http://localhost:1234/login-callback" },
  PostLogoutRedirectUris = { "http://localhost:1234/logout-callback" },
  AllowedCorsOrigins = { "http://localhost:1234" },
  AllowedScopes = { "openid", "profile", "bejebeje-api-local" },
  AllowOfflineAccess = true,
  RefreshTokenUsage = TokenUsage.ReUse,
}

Where am I going wrong?

J86
  • 14,345
  • 47
  • 130
  • 228
  • do you have a redirect to https somewhere? oidc-client.js seems to save the state in localstorage, which is different between http and https. – Dominik Oct 07 '19 at 07:53
  • How can I investigate if I am? I'll try and see if that is the case with Fiddler. – J86 Oct 07 '19 at 09:12

2 Answers2

2

Try this here.. https://github.com/JwanKhalaf/Bejebeje.React/blob/bug/fix-no-code-response-on-redirect/src/components/AuthProvider/AuthProvider.jsx#L15

Basically, when you are sending a login request from the client (OIDC-client.js), the client sends a unique identifier (State) along with the login request in the URL. The client saves this value in the storage option you choose (local/session). After successful sign-in, server issues tokens in the response redirect url and it also includes the unique identifier client initially sent in login request. Watch the login request url and response url in chrome Developer Tools.

When OIDC Client Library receives the sign-in response, it grabs the unique identifier from the URL and matches with the value it kept in the local/session storage at the time of login request. If they don't match, the client will not accept the token issued by the server.

This is just one level of additional security to make sure, the client is not accepting any tokens issued by any random server or any bookmarked URL.

Hope this helps.!!

usermanager.signinRedirect({ state: { bar: 15 } });
hashbytes
  • 769
  • 1
  • 8
  • 26
  • 1
    I'm going to sound really stupid, but that link when you've put next to 'try this', just links to Line 15 in my code? Was that intentional? I've only just had the chance to get back to this issue, been having some work-related issues, apologies for the delay in trying this out. – J86 Sep 14 '19 at 08:16
2

I had to add response_mode: 'query' to the UserManager.

var mgr = new Oidc.UserManager({ response_mode: 'query' });
mgr.signinRedirectCallback().then(res => {
    window.location = "/signin-callback";
}).catch(error => {
    window.location = "/";
})
Charitha Goonewardena
  • 4,418
  • 2
  • 36
  • 38