oidc-react
oidc-react copied to clipboard
Question: How to work around useAuth + "AuthContext cannot be undefined" error in SSR situation
In v1.5.1, there was a change introduced here to check that AuthContext
is defined: https://github.com/bjerkio/oidc-react/pull/576. It's a reasonable change, but it causes an issue for us. We explicitly avoid rendering these components during SSR because there's no window object, and therefore the library won't handle it.
The code is approximately this:
// Near the root component of the app
if (typeof window !== 'undefined') {
return <AuthProvider {...oidcConfig}>{children}</AuthProvider>
}
return children
This worked because before 1.5.1 useAuth
didn't require the AuthContext
to be defined. Now that it does require it this won't work anymore. I cannot optionally call useAuth
because that will break the rules of hooks and cause errors from React itself.
Any thoughts? I'm happy to adjust my approach to accommodate the change, but I'm not sure what that adjustment would even be.
It's causing an issue for us as well, we have a setup where OIDC authentication is optional (they can either use a pre-shared token, or use OIDC but not both). This is defined at the application level.
We previously weren't rendering the <AuthProvider>
at all in "token" mode, but useAuth() now throws an error (understandably). We cant render an <AuthProvider>
with no props due to this change
An option I've used, similar to a situation I've found in a previous iteration with the Apollo provider (https://www.apollographql.com/docs/react/development-testing/testing/#the-mockedprovider-component), was to create a render method that could wrap the component being tested in an <AuthProvider>
component.
So in my shared test utils index.tsx
I have:
export function renderWithAuthProvider(ui: React.ReactElement): RenderResult {
const oidcOptions: AuthProviderProps = {};
return render(
<AuthProvider {...oidcOptions}>
{ui}
</AuthProvider>
);
}
For simplicity's sake (and because I haven't touched the code base in a while and am rusty with TS) I didnt pass in any options and just set them to a default object, but I'd imagine you could pass in a mock specific to the test if needed.
As for the test itself, this will now pass:
test("renders the header", () => {
const { container } = renderWithAuthProvider(<Header />);
expect(container).toBeInTheDocument();
});
Thanks for the tip @MiguelAlho but I'm not sure I follow. Are you suggesting a chance to oidc-react
to create another render method that can deal with the situation?
The issue I'm facing is that the underlying oidc-client-js
library requires that window
is defined. To work around that, I would simply not render the AuthProvider
until I knew window
was defined. That worked until the context was required to be defined. I guess another way around it is I could add params to useAuth
to accept if it's doing SSR, and not fail in that situation. Might be a good change to make anyway.
not really - my suggestion was from a consumer point-of-view - (someone writing tests on a component that uses useAuth
). It worked for me (partially) so that's why I suggested it as a possible solution. I unfortunately hadn't considered the SSR aspect of this.
Also, reagrding the wrapping with AuthProvider
, there are some UnhandledPromiseRejectionWarning
s showing up for me now, potentially due to how I've configured the options. In this case, i get Error: No authority or metadataUrl configured on settings
in the console (but doesn't fail the test), so my suggestion might not even be a valid solution.
I've come up with a potential solution to the issue. I genuinely don't know if I like it or not. It consists of two parts:
- Make
AuthContext
never be in anundefined
state. - Add an
ssr
flag touseAuth
.
First a default AuthContext
is created that says oidc-react
is loading, and plugs in dummy values everywhere else. This works because once the actual AuthProvider
is run, it will replace it with a legit AuthContext
to be used later in the system.
Second, in useAuth
it accepts an ssr
flag, which simply says if it's server-side or not. Then there are a few cases:
- ssr = true, context = defaultContext => returns defaultContext. Since effects are not run server-side, none of the extra auth code will be running.
- ssr = false, context = defaultContext => throws error because we haven't loaded the proper
AuthContext
yet. - ssr = true, context != defaultContext => returns context. In the case that something useful for SSR is given in the future, it would be valuable to have the real context. Right now though
oidc-client
and thusoidc-react
depend on running client-side, so this information would not be valuable. - ssr = false, context != defaultContext => returns context since this is a valid state.
I have a demo I opened up to review it. Again, not really sure about it, but it does seem to work for me.
For now I'm going to use this change in my projects and see how it fares. I'll update and make the PR official when I feel confident with it.
Hey!
I'm closing this for now, but feel free to open a new issue or let me know if you feel this should be re-opened!