IdentityModel / oidc-client-js

OpenID Connect (OIDC) and OAuth2 protocol support for browser-based JavaScript applications
Apache License 2.0
2.43k stars 842 forks source link

Custom store for UserManager #243

Closed pellea closed 7 years ago

pellea commented 7 years ago

Hello,

In a React app using server side rendering I cannot change the store correctly. I tried to update the store for the UserManager as stated here:

brockallen commented on Nov 13, 2016 Sure, for storage that's extensible. Just implement the WebStorageStateStore API and set the userStore on the UserManagerSettings.

Here what I'm doing:

The "new" store:

import { InMemoryWebStorage } from 'oidc-client';
import MemoryStorage from 'memorystorage'

export default class CustomWebStorageStore implements InMemoryWebStorage
{
    private _storage: MemoryStorage;

    constructor(){
        this._storage = new MemoryStorage('my-app');
    }

    getItem(key) {
        return this._storage.getItem(key);
    }

    setItem(key, value){
        this._storage.setItem(key, value);
    }   

    removeItem(key){
        this._storage.removeItem(key);
    }

    get length() {
        return this._storage.length();
    }

    key(index) {
        return this._storage.key(index);
    }
}

The config for the UserManager:

import { UserManager, WebStorageStateStore, WebStorageStateStoreSettings } from 'oidc-client';
import CustomWebStorageStore  from './CustomWebStorageStore';

const myStore: CustomWebStorageStore = new CustomWebStorageStore();

const settings: WebStorageStateStoreSettings = 
{
  prefix: "oidc.", 
  store: myStore
};

// create a user manager instance
const config = {
  client_id: 'js',
  redirect_uri: `http://localhost:5002/auth/callback`,
  response_type: 'id_token token',
  scope: 'openid profile',
  authority: 'http://localhost:5000',
  post_logout_redirect_uri: `http://localhost:5002/login`,
  silent_redirect_uri: `http://localhost:5002/silent_renew.html`,
  automaticSilentRenew: true,
  filterProtocolClaims: true,
  loadUserInfo: true,
  userStore: new WebStorageStateStore(settings)
}

const userManager : UserManager = new UserManager(config);

export default userManager;

If I write this following line userManager.signinRedirect(); , it crashes automatically with this error:

Exception: Call to Node module failed with error: Prerendering failed because of error: ReferenceError: localStorage is not defined
at Function.get (\node_modules\oidc-client\lib\oidc-client.min.js:1:12917)
at new t (\node_modules\oidc-client\lib\oidc-client.min.js:1:11166)
at e.t (\node_modules\oidc-client\lib\oidc-client.min.js:1:8087)
at new e (\node_modules\oidc-client\lib\oidc-client.min.js:74:15427)
at new e (\node_modules\oidc-client\lib\oidc-client.min.js:74:5255)
at Object.<anonymous> (\ClientApp\dist\main-server.js:4521:20)
at __webpack_require__ (\ClientApp\dist\main-server.js:20:30)
at Object.defineProperty.value (\ClientApp\dist\main-server.js:4433:21)
at __webpack_require__ (\ClientApp\dist\main-server.js:20:30)
at Object.<anonymous> (\ClientApp\dist\main-server.js:215:15)

It crashes when accessing the Global.localstorage as if my custom store is not taking into account or undefined.

Any idea why?

Thanks.

brockallen commented 7 years ago

I have a feeling that's the stateStore that's failing, not the userStore. The stateStore is where state is persisted across OIDC authorization requests.

pellea commented 7 years ago

Okay I missed that one ... Thanks.

I have the following error now: Call to Node module failed with error: Prerendering failed because of error: ReferenceError: XMLHttpRequest is not defined It's normal since there is no XMLHttpRequest server side. Is it possible to configure something to make it run (even if it is not working on server side, only not making it crash completely) ?

brockallen commented 7 years ago

IIRC, I tried to design for that to be injectable as well. But just so you know, this library was designed for browser-based clients.

pellea commented 7 years ago

Yes I know I read your other comment on the other Github issue. I don't want the OIDC to work server side, only to not crash.

But the problem here is, even if the code is not call at all, there is an error. By simply adding this line of code userManager.signinRedirect(); it make the whole application crashes immediately.

I tried checking if the code is running on the server side with if (this.context.isServer) { ... } but it doesn't help.

Is there anything I can do to solve this? Do you have any usage sample with a DI?

brockallen commented 7 years ago

Is there anything I can do to solve this

I'm happy to accept a PR to address the crash. Fundamentally there's a requirement on some of the global window properties, so if you can improve on how I tried to decouple it then that's great.

pellea commented 7 years ago

What does this means :

oidc-client.min.js:1 no user storageString
oidc-client.min.js:1 No matching state found in storage
oidc-client.min.js:1 user not found in storage

Does that means the storage is not working properly? The authentication seems to work correctly.

Here is a more complete log:

oidc-client.min.js:1automaticSilentRenew is configured, setting up silent renew
oidc-client.min.js:1UserManager.getUser
oidc-client.min.js:1_loadUser
oidc-client.min.js:1WebStorageStateStore.get user:http://localhost:5000:js
oidc-client.min.js:1monitorSession is configured, setting up session monitor
oidc-client.min.js:1UserManager.getUser
oidc-client.min.js:1_loadUser
oidc-client.min.js:1WebStorageStateStore.get user:http://localhost:5000:js
oidc-client.min.js:1UserManager.getUser
oidc-client.min.js:1_loadUser
oidc-client.min.js:1WebStorageStateStore.get user:http://localhost:5000:js
3oidc-client.min.js:1no user storageString
3oidc-client.min.js:1user not found in storage
client.js:62[HMR] connected
Navigated to http://localhost:5002/admin
oidc-client.min.js:1 UserManager.getUser
oidc-client.min.js:1 _loadUser
oidc-client.min.js:1 WebStorageStateStore.get user:http://localhost:5000:js
oidc-client.min.js:1 no user storageString
oidc-client.min.js:1 user not found in storage
oidc-client.min.js:1 UserManager.getUser
oidc-client.min.js:1 _loadUser
oidc-client.min.js:1 WebStorageStateStore.get user:http://localhost:5000:js
oidc-client.min.js:1 no user storageString
oidc-client.min.js:1 user not found in storage
oidc-client.min.js:1 UserManager.signinRedirect
oidc-client.min.js:1 _signinStart
oidc-client.min.js:1 got navigator window handle
oidc-client.min.js:1 OidcClient.createSigninRequest
oidc-client.min.js:1 MetadataService.getAuthorizationEndpoint
oidc-client.min.js:1 MetadataService._getMetadataProperty authorization_endpoint
oidc-client.min.js:1 MetadataService.getMetadata
oidc-client.min.js:1 getting metadata from http://localhost:5000/.well-known/openid-configuration
oidc-client.min.js:1 JsonService.getJson http://localhost:5000/.well-known/openid-configuration
oidc-client.min.js:1 HTTP response received, status 200
oidc-client.min.js:1 json received
oidc-client.min.js:1 metadata recieved
oidc-client.min.js:1 Received authorization endpoint http://localhost:5000/connect/authorize
oidc-client.min.js:1 SigninState.toStorageString
oidc-client.min.js:1 WebStorageStateStore.set d484c92c89964793b81cfa7d72b4a818
oidc-client.min.js:1 got signin request
oidc-client.min.js:1 RedirectNavigator.navigate
Navigated to http://localhost:5000/account/login?returnUrl=%2Fconnect%2Fauthorize%2Flogi…484c92c89964793b81cfa7d72b4a818%26nonce%3De63e1a63ce4949c2ba7e86a4f5529bb7
Navigated to http://localhost:5002/auth/callback
oidc-client.min.js:1automaticSilentRenew is configured, setting up silent renew
oidc-client.min.js:1UserManager.getUser
oidc-client.min.js:1_loadUser
oidc-client.min.js:1WebStorageStateStore.get user:http://localhost:5000:js
oidc-client.min.js:1monitorSession is configured, setting up session monitor
oidc-client.min.js:1UserManager.getUser
oidc-client.min.js:1_loadUser
oidc-client.min.js:1WebStorageStateStore.get user:http://localhost:5000:js
oidc-client.min.js:1UserManager.getUser
oidc-client.min.js:1_loadUser
oidc-client.min.js:1WebStorageStateStore.get user:http://localhost:5000:js
oidc-client.min.js:1 UserManager.signinRedirectCallback
oidc-client.min.js:1 RedirectNavigator.url
oidc-client.min.js:1 _signinEnd
oidc-client.min.js:1 OidcClient.processSigninResponse
oidc-client.min.js:1 UrlUtility.parseUrlFragment
oidc-client.min.js:1 WebStorageStateStore.remove d484c92c89964793b81cfa7d72b4a818
3oidc-client.min.js:1 no user storageString
oidc-client.min.js:1 No matching state found in storage
t.error @ oidc-client.min.js:1
(anonymous) @ oidc-client.min.js:1
3oidc-client.min.js:1 user not found in storage
redux-oidc.js:1 Uncaught (in promise) Error: Error handling redirect callback: No matching state found in storage
    at r.n.onRedirectError (redux-oidc.js:1)
    at redux-oidc.js:1

I use a modified version of oidc-client-js that replaces the XMLHttpRequest by fecth (https://github.com/pellea/oidc-client-js/commit/0c35d47585c1d4ee947cd0d33304a9eb3c1181c2)

brockallen commented 7 years ago

i wonder on your callback page what the code looks like?

pellea commented 7 years ago

The callback page is the callback from redux-oidc. It was working fine when the library allow the automatic auth flow. I don't know what changes in the latest release. @maxmantz

It seems that the store has a key but no value associated with the key. And therefore the following error in OidcClient.js script file (storedStateString is undefined):

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

The stateStore.remove(response.state) is undefined (there is no value).

Here is my callback URL returned and well fed: /auth/callback#id_token=...&access_token=...&token_type=Bearer&expires_in=3600&scope=openid%20profile&state=...&session_state=...

pellea commented 7 years ago

My bad there was an issue with my custom store.

Okay, I think I finally got it working properly. I'll come back here after more tests and close the issue if it is okay.

tonven commented 7 years ago

@pellea Hi. Can you provide your custom storage. Tried to use the one you provided before, but still gets an error with sessionStorage is not defined.

pellea commented 7 years ago
import { InMemoryWebStorage } from "oidc-client-fetch";

export default class LocalWebStorageStore implements InMemoryWebStorage
{
    private _storage: any;

    constructor() {
        if (typeof localStorage === "undefined" || localStorage === null) {
            var LocalStorageDep: any = require("node-localstorage");
            var LocalStorage: any = LocalStorageDep.LocalStorage;
            this._storage = new LocalStorage("./oidc-storage");
        } else {
            this._storage = window.localStorage;
        }
    }

    getItem(key) {
        return this._storage.getItem(key);
    }

    setItem(key, value) {
        this._storage.setItem(key, value);
    }

    removeItem(key) {
        this._storage.removeItem(key);
    }

    get length() {
        return this._storage.length();
    }

    key(index) {
        return this._storage.key(index);
    }
}