Keycloak acts as if its uninitialized after a hard page refresh - reactjs

First of all, thanks in advance to anyone who reads my question and comments. I have a CRA app that is using keycloak-js and ReactKeycloakProvcer from `#react-keycloak/web. When you first load the application page and login, keycloak is initialized correctly and functions as it should. The provider gets an instance of KC in a pretty standard way.
import keycloak from './authentication/keycloak'
const KeycloakProviderBlock = ({children}) => {
return (
<ReactKeycloakProvider authClient={keycloak} initOptions={{onLoad: 'login-required'}}>
{children}
</ReactKeycloakProvider>
);
};
Later in my axios wrapper, I am pulling the KC token out to add to all requests as the bearer token like this:
import keycloak from "./authentication/keycloak";
const {authenticated} = keycloak;
if (authenticated) {
client.defaults.headers.common = {
...client.defaults.headers.common,
Authorization: `Bearer ${keycloak.token}`,
};
} else {
logger.error("Request client used before KeyCloak initialized");
}
My keycloak file just returns a new instance of KC --> contents of /authentication/keycloak.js
import Keycloak from "keycloak-js";
const keycloak = new Keycloak({
realm: process.env.REACT_APP_KEYCLOAK_REALM,
url: process.env.REACT_APP_KEYCLOAK_URL,
clientId: process.env.REACT_APP_KEYCLOAK_CLIENT,
})
export default keycloak
Everything works like it should until the user hard refreshes the page. When the page reloads keycloak.authenticated is not present on the KC object, so all HTTP calls fail because there is no Bearer token.
I'm using keycloak-js version 15.0.2. Any/all thoughts appreciated.

I figured out how to fix this and figured I would post the answer in case it helps anyone else. It turns out when the page refreshes, it takes KC longer to restore the session information from the cookie than it does for the code to run. So there was a race condition when the page reloaded it was trying to reach the backend before KC had a chance to validate the user was indeed logged in. It turns out that the KeycloakProvider emits events and tells you when the token is refreshed. The events were not firing because I had the KCProvider wrapped in a JSX component, so the events were not properly bound. I removed the unnecessary block and the events started to fire. From there, it was pretty easy to display a Loading throbber and block the rest of the components from rendering until the provider actually got the onReady event. The new code looks like this:
In App.js
// Keycloak
onKeycloakEvent = (event, error) => {
console.log('onKeycloakEvent', event, error)
console.log(`Keycloak Event ${event}`);
if(event && event === 'onReady'){
this.setState({keycloakReady: true})
}
}
onKeycloakTokens = (tokens) => {
console.log('onKeycloakTokens', tokens)
}
...
In the Render Pass the functions to the provider:
<ReactKeycloakProvider authClient={keycloak} initOptions={{onLoad: 'login-required'}}
keycloak={keycloak}
onEvent={this.onKeycloakEvent}
onTokens={this.onKeycloakTokens}>
<SplashScreen keycloakReady={keycloakReady}>
....
</SplashScren>
</ReactKeycloakProvder>
Then in the SplashScreen only render children if KC is ready:
import React, {Component, PureComponent, ReactChildren} from "react";
import LoadingSpinner from "../navigation/LoadingSpinner";
type PassedProps = {
keycloakReady: boolean;
}
class SplashScreen extends PureComponent<PassedProps, any> {
constructor(props: PassedProps) {
super(props);
}
render() {
console.log(`SplashScreen keycloak ready ${this.props.keycloakReady}`);
if(!this.props.keycloakReady){
return <LoadingSpinner/>
}else{
return this.props.children
}
}
}
export default SplashScreen

Related

Next.js server side props not loading in time

I'm using supabase and trying to load the user session on the server side. If you refresh the page, it catches there is a user but not on first load (e.g. like when coming from a magic link). How can I ensure it does load before he page?
List item
Here is the page:
import router from "next/router";
import { supabase } from "../utils/supabaseClient";
function Home() {
const user = supabase.auth.user()
if (user){
//router.push('/admin') failsafe, not ideal
}
return (
<div className="min-h-screen bg-elkblue dark:bg-dark-pri">
marketing
</div>
);
}
export async function getServerSideProps({ req }) {
const { user } = await supabase.auth.api.getUserByCookie(req);
if (user) {
return {
redirect: {
destination: "/admin",
permanent: false,
},
};
}
return {
props: { }, // will be passed to the page component as props
};
}
export default Home;
You can use the auth-helpers for help with server-side rendering https://github.com/supabase-community/supabase-auth-helpers/blob/main/src/nextjs/README.md#server-side-rendering-ssr---withpageauth
Do note that it however needs to render the client first after OAuth because the server can't access the token from the URL fragment. The client will then read the token from the fragment and forward it to the server to set a cookie which can then be used for SSR.
You can see an example of that in action here: https://github.com/vercel/nextjs-subscription-payments/blob/main/pages/signin.tsx#L58-L62

How to send firebase refreshToken ? React

I am using firebase for the first time. (React app) I have 3 social Auth Provider for sign-in. Google, Facebook and Apple. Everything works great until here. But after 1 hour my token expires and I have to sign-out and sign-in again for refreshing my token. I saved the expiration time to my localStorage to check if token expires or not, if yes I invoke the signOut() function manually. But it doesn't solve the problem and not a good approach. I can't find how to refreshToken in firebase. And also, am I need to check expires again and send refreshToken or I have to refresh token on every time page refresh ?
import React from 'react'
import { useHistory } from "react-router";
import auth from "../utils/Auth";
export const useSocialAuth = () => {
const history = useHistory();
const providerFunc = (socialProvider:any) => {
const provider = socialProvider();
provider
.then((result: any) => {
console.log(result)
auth.login(() => {
localStorage.setItem('exp', result.user._delegate.stsTokenManager.expirationTime)
localStorage.setItem("userID", result.user.uid);
localStorage.setItem("tocaToken", result.user.multiFactor.user.accessToken);
history.push("/");
});
})
.catch((err:any) => console.log(err));
}
return providerFunc;
};
I solved the refresh token problem. All you guys need to add:
firebase.auth().currentUser.getIdToken(true)
When you make call from a browser .getIdToken(true) will automatically refresh your token. Make call like this:
firebase.auth().currentUser.getIdToken(/ forceRefresh / true)
.then(function(idToken) {
}).catch(function(error) {
});
More info: https://firebase.google.com/docs/reference/js/v8/firebase.User#getidtoken

NextJS App with Auth0 and Redux failing to retrieve Auth session on manual refresh

Problem: if I refresh a page, getInitialProps trigger but it gets me the error describe on the bottom of the page which points out an issue going all the way to my redux wrapper on /_app.js, which I believe to be the issue. This behavior only happens on refresh page. If I go from a page that has no getInitialProps and then navigate client side to the page with getInitialProps it works fine. If I then do a manual refresh of the page it triggers the error.
This is my setup for redux and I've tried it with the Class based version with same results so that's not the issue.
/_app.js
import { Provider } from "react-redux";
import withRedux from "next-redux-wrapper";
import { initStore } from "../store";
import "./styles.scss";
const MyApp = props => {
const { Component, pageProps, store } = props;
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
);
};
MyApp.getInitialProps = async ({ Component, ctx }) => {
const pageProps = Component.getInitialProps
? await Component.getInitialProps(ctx)
: {};
return { pageProps };
};
export default withRedux(initStore)(MyApp);
this is the getInitialProps on the index.js page
/pages/index.js
OpenApplications.getInitialProps = async ({ store }) => {
const stateDB = await getApplications();
// populate state with all applications from DB
await store.dispatch(populateApplicationsState(stateDB));
return store;
};
this is the internal API setup with Auth
/api/applications.js
import auth0 from "../../../../lib/auth/auth0";
// auth0.requireAuthentication ensures that only authenticated users access this internal API path
export default auth0.requireAuthentication(async (req, res) => {
// get user profile from Auth0 on the server side
const { user } = await auth0.getSession(req);
process the rest of the stuff...
})
this is the code behind the Auth0 init config
.../auth/auth0.js
import { initAuth0 } from "#auth0/nextjs-auth0";
import config from "./config";
export default initAuth0({
clientId: config.AUTH0_CLIENT_ID,
clientSecret: config.AUTH0_CLIENT_SECRET,
scope: config.AUTH0_SCOPE,
domain: config.AUTH0_DOMAIN,
redirectUri: config.REDIRECT_URI,
postLogoutRedirectUri: config.POST_LOGOUT_REDIRECT_URI,
session: {
cookieSecret: config.SESSION_COOKIE_SECRET,
cookieLifetime: config.SESSION_COOKIE_LIFETIME,
cookieSameSite: "lax",
storeIdToken: true,
storeRefreshToken: true,
storeAccessToken: true
}
});
This is the Application tab on Chrome Dev Tools and it shows the a0state and a0session. When I refresh, the state disappears but the cookie stays. On an unprotected page it then comes back after I assume making a call to check the session is valid but on a protected page it goes into the error above.
error if requireAuthtentication is taken OFF
error if requireAuthentication is ON
So my suspicion is that whenever a manual page refresh is onset, auth0 loses the state and it doesn't pass it in the req to the API so that it fails the auth check and also it fails to get the session from the user. If I remove the auth0.requireAuthentication, it still fails because I'm dependent on the user which comes from auth0.getSession(req); which fails nonetheless and that triggers an error anyway because user comes undefined. What I think it's happening is that the session is not being passed on correctly due to the Redux wrapper setup that doesn't pass the necessary info or triggers the necessary Auth for that info to be there. At this point I'm a bit lost as all these tools are new to me and I'm not sure where it's going wrong and where to fix it and since it's 3 different libraries interacting it gets even harder. Help figure this super annoying bug :)
I checked everywhere and couldn't find a solution or a similar setup to get a comparison and find out a potential mistake I've done on my setup. Everything is pretty much like the examples on the docs for the different tools next-wrapper-redux/nextjs-auth0/next

How to keep or resupply React Context in a Gatsby site

I use React Context API to store the information that a user is authenticated.
In development mode when I type in any URL that redirects to the 404 error page the context data is lost. When I navigate to a valid page a previously logged in user is not logged in any more.
EDIT: I just tested this with gatsby build and gatsby serve. A built gatsby site keeps the context when redirecting to 404 error page. But the context is still lost when navigating to completely different URL such as www.google.com.
Now my question is: How do I resupply the context with the login information without having the user be manually log in again?
Here is my AuthContextProvider wrapper class:
export class AuthContextProvider extends React.Component {
constructor(props) {
super(props);
this.state = { user: {} };
}
// ...
render() {
return (
<AuthContext.Provider value={{ getUser: this.getUser, setUser: this.setUser }}>
{this.props.children}
</AuthContext.Provider>
);
}
}
I wrap my whole app with the Context Provider in a root layout:
const RootLayout = ({ children }) => {
return (
<AuthContextProvider>
{children}
</AuthContextProvider>
);
}
React Context is about providing some data to one or more child components without having to pass the data down through intermediary components. There's no built-in mechanism for persisting state between page loads, so you'll need to reach for another tool for that.
If you haven't already implemented your authentication layer, you'll want to look into how that will work. There are a number of strategies for maintaining that state, even just within using cookie-based storage. JWT (JSON Web Token) are a popular method that will let you store signed user and client-readable data in the cookie at the cost of requiring a bit more work to manage expiration/renewal and having a larger payload. Assuming that's the approach you took, you might do something like this:
import React from "react";
import jwt from "jsonwebtoken"; // Add jsonwebtoken via npm/yarn
function getCookieValue(a) {
var b = document.cookie.match('(^|[^;]+)\\s*' + a + '\\s*=\\s*([^;]+)');
return b ? b.pop() : '';
}
const AUTH_PUBLIC_KEY = "your JWT public key here"
export const AuthContext = React.createContext();
export class AuthContextProvider extends React.Component {
state = {
authenticated: false,
userid: null,
};
componentDidMount() {
jwt.verify(getCookieValue("session"), AUTH_PUBLIC_KEY, (err, session) => {
if (!err && session.userid) {
this.setState({ userid: session.userid, authenticated: true })
}
})
}
// Important: REMOVE THIS AFTER TESTING/DEV
toggleLogin = () => {
this.setState(state => ({
authenticated: !state.authenticated,
userid: 2,
}));
}
render() {
return (
<AuthContext.Provider
value={{
...this.state,
toggleLogin: this.toggleLogin,
}}
>
{this.props.children}
</AuthContext.Provider>
);
}
}
This will parse the JWT token in the session cookie when the AuthContextProvider is mounted and update the state with the userid value stored in the JWT if one is present.
You will probably want to wrap the Gatsby App with this component, which you can do from gatsby-browser.js and gatsby-ssr.js files (create them in the root of your repo if you don't have them yet):
// gatsby-browser.js
import React from "react"
import AuthContextProvider from "components/AuthContextProvider"
export const wrapRootElement = ({ element }) =>
<AuthContextProvider>{element}</AuthContextProvider>
// gatsby-ssr.js
import React from "react"
export { wrapRootElement } from "./gatsby-browser"
You will still need to handle generating the JWT token (probably from a backend that is handling authentication) and if it's not already being persisted in a cookie you can access from the browser you will need to handle creation of that cookie at the relevant point in your application lifecycle.
I hope this helps you or others. The blog post below describes how you need to use gatsby-browser.js to wrap the root element in the provider so that it doesn't reset it on page change.
https://www.gatsbyjs.org/blog/2019-01-31-using-react-context-api-with-gatsby/
You have 3 possibilities:
web storage aka localStorage or sessionStorage (easiest, least secure)
session cookies (secure, requires backend server)
json web tokens (JWT) (most secure, requires backend server)
An excellent read about background infromation is this blog on dev.to.
1. web storage such as localStorage
This is considered to be the least secure option secure option. Do not save personal data such as email adresses here. Never ever save sensitive information such as credit card information and such.
This question describes how to use it:
var testObject = { 'one': 1, 'two': 2, 'three': 3 };
// Put the object into storage
localStorage.setItem('testObject', JSON.stringify(testObject));
// Retrieve the object from storage
var retrievedObject = localStorage.getItem('testObject');
console.log('retrievedObject: ', JSON.parse(retrievedObject));
2. cookies or session cookies
For Express you can use express-ession. Other web servers have similar middleware. The point is to supply the user info within a cookie as described on MDN.
3. json web tokens
This is similar to cookies but uses JSON web tokens. #coreyward gave an excellent answer. You can also read more in this blog post.

Why is auth token not set in mobx store after login, and reroute to next component

I am using CoreUI with React Router DOM, React Router DOM config, Mobx and Mobx react.
I have written a login function in my store which stores the auth token from the server in session storage (using sessionStorage.setItem('token',token)) after successful login.
The problem, is that the next component I route to after login does a javascript fetch on componentDidMount, and sends the bearer auth token in the headers.
However, the issue that I am running into is that the auth token is undefined when the fetch occurs (sessionStorage.getItem('token')). It only shows up after I refresh the page. It is like the component loads and is rendered before the auth token has been saved.
I wonder if this is due to asynchronous calls.
In my login component I have this function:
logIn = async () => {
const { sessionStore: ss } = this.props.RootStore;
let login = await this.ss.login();
if(login) {
this.props.history.push('/dashboard');
}
}
Then, in my next component (Dashboard), I have:
componentDidMount() {
const { companyStore : company, sessionStore : ss } = this.props.RootStore;
if(ss.companySetup === false) {
company.getDefaultCompany('set');
}
}
In the function getDefaultCompany, I check if sessionStorage.getItem('token') is set, and if so, add it to the header. But it comes back undefined. If I refresh the page, the token is set and goes with the fetch call.
I am stumped.

Resources