React and reCAPTCHA v3 - reactjs

Is there any easy way to use reCAPTCHA v3 in react? Did a google search an can only find components for v2. And only react-recaptcha-v3 for v3.
But I get an error Invalid site key or not loaded in api.js when I try to use the component.

Hey you don't need a package, its just an unnecessary package you don't need.
https://medium.com/#alexjamesdunlop/unnecessary-packages-b3623219d86
I wrote an article about why you shouldn't use it and another package.
Don't rely on some package! Rely on google instead :)
const handleLoaded = _ => {
window.grecaptcha.ready(_ => {
window.grecaptcha
.execute("_reCAPTCHA_site_key_", { action: "homepage" })
.then(token => {
// ...
})
})
}
useEffect(() => {
// Add reCaptcha
const script = document.createElement("script")
script.src = "https://www.google.com/recaptcha/api.js?render=_reCAPTCHA_site_key"
script.addEventListener("load", handleLoaded)
document.body.appendChild(script)
}, [])
return (
<div
className="g-recaptcha"
data-sitekey="_reCAPTCHA_site_key_"
data-size="invisible"
></div>
)

I am teaching myself React + TypeScript and this is what I came up with to implement recaptcha v3.
I wanted a simple solution that would allow me to:
get the token dynamically only when the form is submitted to avoid timeouts and duplicate token errors
use recaptcha only on some components for privacy reasons (eg. login, register, forgot-password) instead of globally defining recaptcha api.js in index.html
require the least code possible to implement in a component
reCAPTCHA.ts
declare global {
interface Window {
grecaptcha: any;
}
}
export default class reCAPTCHA {
siteKey: string;
action: string;
constructor(siteKey: string, action: string) {
loadReCaptcha(siteKey);
this.siteKey = siteKey;
this.action = action;
}
async getToken(): Promise<string> {
let token = "";
await window.grecaptcha.execute(this.siteKey, {action: this.action})
.then((res: string) => {
token = res;
})
return token;
}
}
const loadReCaptcha = (siteKey: string) => {
const script = document.createElement('script')
script.src = `https://www.recaptcha.net/recaptcha/api.js?render=${siteKey}`
document.body.appendChild(script)
}
To use this class declare it as a property in the component:
recaptcha = new reCAPTCHA((process.env.REACT_APP_RECAPTCHA_SITE_KEY!), "login");
And on form submit get the token that you need to pass to backend:
let token: string = await this.recaptcha.getToken();
To verify the token on the backend:
recaptcha.ts
const fetch = require("node-fetch");
const threshold = 0.6;
export async function validateRecaptcha(recaptchaToken: string, expectedAction: string) : Promise<boolean> {
const recaptchaSecret = process.env.RECAPTCHA_SECRET_KEY;
const url = `https://www.recaptcha.net/recaptcha/api/siteverify?secret=${recaptchaSecret}&response=${recaptchaToken}`;
let valid = false;
await fetch(url, {method: 'post'})
.then((response: { json: () => any; }) => response.json())
.then((data: any)=> {
valid = (data.success && data.score && data.action && data.score >= threshold && data.action === expectedAction);
});
return valid;
}
I have very limited experience with JS/TS and React but this solution does work for me. I welcome any input on improving this code.

You can use react-google-recaptcha3 npm package (size: ~5 KB)
npm i react-google-recaptcha3
Usage
import ReactRecaptcha3 from 'react-google-recaptcha3';
const YOUR_SITE_KEY = '';
function App() {
// load google recaptcha3 script
useEffect(() => {
ReactRecaptcha3.init(YOUR_SITE_KEY).then(
(status) => {
console.log(status);
}
);
}, [])
}
Now on form submit you need to generate token and then append it to your form data
const submit = () => {
const formData = { name: "John", lastname: "Doe" }
ReactRecaptcha3.getToken().then(
(token) => {
console.log(token);
formData.token = token;
// send request to backend
fetch(url, { method: 'POST', body: JSON.stringify(formData) }).then(...)
},
(error) => {
console.log(error);
}
);
};
Now in backend you need to validate token
const request = require('request-promise');
const secretKey = YOUR_RECAPTCHA_SECRET_KEY;
const userIp = 'USER_IP';
request.get({
url: `https://www.google.com/recaptcha/api/siteverify?secret=${secretKey}&response=${recaptchaToken}&remoteip=${userIp}`,
}).then((response) => {
// If response false return error message
if (response.success === false) {
return res.json({
success: false,
error: 'Recaptcha token validation failed'
});
}
// otherwise continue handling/saving form data
next();
})
Stackblitz example

Try this one!
https://github.com/t49tran/react-google-recaptcha-v3
npm install react-google-recaptcha-v3

You can also create your own custom hook useReCaptcha with React (Typescript):
// hooks/useReCaptcha.ts
import { RECAPTCHA_KEY, RECAPTCHA_TOKEN } from 'config/config'
import { useEffect, useState } from 'react'
const showBadge = () => {
if (!window.grecaptcha) return
window.grecaptcha.ready(() => {
const badge = document.getElementsByClassName('grecaptcha-badge')[0] as HTMLElement
if (!badge) return
badge.style.display = 'block'
badge.style.zIndex = '1'
})
}
const hideBadge = () => {
if (!window.grecaptcha) return
window.grecaptcha.ready(() => {
const badge = document.getElementsByClassName('grecaptcha-badge')[0] as HTMLElement
if (!badge) return
badge.style.display = 'none'
})
}
const useReCaptcha = (): { reCaptchaLoaded: boolean; generateReCaptchaToken: (action: string) => Promise<string> } => {
const [reCaptchaLoaded, setReCaptchaLoaded] = useState(false)
// Load ReCaptcha script
useEffect(() => {
if (typeof window === 'undefined' || reCaptchaLoaded) return
if (window.grecaptcha) {
showBadge()
setReCaptchaLoaded(true)
return
}
const script = document.createElement('script')
script.async = true
script.src = `https://www.google.com/recaptcha/api.js?render=${RECAPTCHA_KEY}`
script.addEventListener('load', () => {
setReCaptchaLoaded(true)
showBadge()
})
document.body.appendChild(script)
}, [reCaptchaLoaded])
// Hide badge when unmount
useEffect(() => hideBadge, [])
// Get token
const generateReCaptchaToken = (action: string): Promise<string> => {
return new Promise((resolve, reject) => {
if (!reCaptchaLoaded) return reject(new Error('ReCaptcha not loaded'))
if (typeof window === 'undefined' || !window.grecaptcha) {
setReCaptchaLoaded(false)
return reject(new Error('ReCaptcha not loaded'))
}
window.grecaptcha.ready(() => {
window.grecaptcha.execute(RECAPTCHA_KEY, { action }).then((token: string) => {
localStorage.setItem(RECAPTCHA_TOKEN, token)
resolve(token)
})
})
})
}
return { reCaptchaLoaded, generateReCaptchaToken }
}
export default useReCaptcha
Then in the login component for example, you can call this custom hook:
// Login.ts
import React from 'react'
import useReCaptcha from 'hooks/useReCaptcha'
const LoginPageEmail = () => {
const { reCaptchaLoaded, generateReCaptchaToken } = useReCaptcha()
const login = async () => {
await generateReCaptchaToken('login') // this will create a new token in the localStorage
await callBackendToLogin() // get the token from the localStorage and pass this token to the backend (in the cookies or headers or parameter..)
}
return (
<button disabled={!reCaptchaLoaded} onClick={login}>
Login
</button>
)
}
export default LoginPageEmail

Related

Next.js | HOC from REST API with Typescript not being read when wrapping a child component to access it's data

I have this HOC line of code from withUser.tsx. When a user is authenticated, the authenticated pages will then be wrapped by it so that the specified user-role will be the one to only have access to pages intended.
import axios, { AxiosError } from "axios";
import { API } from "../config";
import { getCookie } from "../helpers/auth";
const withUser = (Page: any) => {
const WithAuthUser = (props: any): JSX.Element => <Page {...props} />;
WithAuthUser.getInitialProps = async (context: any): Promise<any> => {
const token = getCookie("token", context.req);
let user = null;
let userLinks = [];
if (token) {
try {
const response = await axios.get(`${API}/user`, {
headers: {
authorization: `Bearer ${token}`,
contentType: "application/json",
},
});
console.log("Response in withUser: ", response);
user = response.data.user;
userLinks = response.data.links;
} catch (err: unknown) {
const error = err as AxiosError;
if (error.response?.status === 401) {
user = null;
}
}
}
if (user === null) {
// redirect
context.res.writeHead(302, {
Location: "/",
});
context.res.end();
} else {
return {
...(Page.getInitialProps ? await Page.getInitialProps(context) : {}),
user,
token,
userLinks,
};
}
};
return WithAuthUser;
};
export default withUser;
Now, the above code is not my final writing of TypeScript, I could be wrong but this is how I converted it from JS, please feel free to give me a refactored TSX codes here, here is the JS version:
import axios from "axios";
import { API } from "../config";
import { getCookie } from "../helpers/auth";
const withUser = (Page) => {
const WithAuthUser = (props) => <Page {...props} />;
WithAuthUser.getInitialProps = async (context) => {
const token = getCookie("token", context.req);
let user = null;
let userLinks = [];
if (token) {
try {
const response = await axios.get(`${API}/user`, {
headers: {
authorization: `Bearer ${token}`,
contentType: "application/json",
},
});
console.log("Response in withUser: ", response);
user = response.data.user;
userLinks = response.data.links;
} catch (error) {
if (error.response.status === 401) {
user = null;
}
}
}
if (user === null) {
// redirect
context.res.writeHead(302, {
Location: "/",
});
context.res.end();
} else {
return {
...(Page.getInitialProps ? await Page.getInitialProps(context) : {}),
user,
token,
userLinks,
};
}
};
return WithAuthUser;
};
export default withUser;
But now, when using it when an Authenticated /user page, I could not get any data from the user. It will give me an undefined and for example, user.first_name will not be shown:
import withUser from "../withUser";
const User = ({ user }: any): JSX.Element => (
<div className="flex min-h-screen flex-col items-center justify-center">
{user.first_name}
</div>
);
export default withUser(User);
Any correct ways of implementing this would be very much appreciated. Thanks!
Whether you are in Reactjs or Nextjs, I think there needs to have a correct type definitions of your HOC component in the first place.
First you need to define your HOC component as a React.ComponentType:
const withUser = (ChildComp: React.ComponentType<any | string>) => { /* code follows */ }
you also need to define an interface for the expected values for these"
const token = getCookie("token", context.req);
let user = null;
let userLinks = [];
and when you wrap your child component, say user.tsx, do it like this:
type UserType = {
first_name: string
}
const User: React.SFC<ContainerProps> = ({ user}: UserType)=> (
<h1>{user.first_name ?? "User not found"}</h1>
);
export default withUser(User);
You can read more about here: Create a TypeScript HOC in React
Okay, sorry this was just a bug and I figure out that I did not have any userLinks from the REST API that I was passing in. So I can already consider this question as resolved as I have already fixed it.
Here is the code of my fix:
import axios, { AxiosError } from "axios";
import { API } from "../config";
import { getCookie } from "../helpers/auth";
const withUser = (Page: any) => {
const WithAuthUser = (props: any): JSX.Element => <Page {...props} />;
WithAuthUser.getInitialProps = async (context: any): Promise<any> => {
const token = getCookie("token", context.req);
console.log("token: ", token);
let user = null;
if (token) {
try {
const response = await axios.get(`${API}/user`, {
headers: {
authorization: `Bearer ${token}`,
contentType: "application/json",
},
});
console.log("response: ", response);
user = response.data;
} catch (err: unknown) {
const error = err as AxiosError;
if (error.response?.status === 401) {
user = null;
}
}
}
if (user === null) {
// redirect
context.res.writeHead(302, {
Location: "/",
});
context.res.end();
} else {
return {
...(Page.getInitialProps ? await Page.getInitialProps(context) : {}),
user,
token,
};
}
}
return WithAuthUser;
}
export default withUser;

How to fetch API data from Axios inside the getServerSideProps function in NextJS?

I'm building an App with Next.js, and I need to connect to specific API routes (set up with API Platform) and populate pages with the route's responses.
The API is working fine, but no matter how I try to implement my Axios call inside the getServerSideProps, I always get the same error, ECONNREFUSED, from my Node stack.
I tried to get the data from useEffect() and it's working fine, but I would like to know if there's a way to call it directly in getServerSideProps.
I'm using a Node container for Docker, and the routes are authenticated through a JWT Token (stored in the session and the client cookies for the server-side connection)
Here are is my code:
pages/accounts.js:
export async function getServerSideProps(context) {
const cookies = new Cookies(context.req.headers.cookie)
const adminToken = cookies.get('jwtToken')
const res = await getAllAccounts(adminToken)
return {
props: {
testdata: ''
},
}
}
lib/accounts.js:
import service from '../service'
export const getAllAccounts = async (adminToken) => {
const res = service({ jwtToken : adminToken }).get(`/accounts`).then((response) => {
}).catch((error) => {
console.dir(error)
})
}
HTTP wrapper:
import axios from 'axios';
import jwt_decode from "jwt-decode";
import mockAdapter from 'axios-mock-adapter';
const service = ({ jwtToken = null, store = null, mockURL = null, mockResponse = null, multipart = false } = {}) => {
const options = {};
options.baseURL = process.env.NEXT_PUBLIC_API_URL + '/api';
if(multipart === true) {
options.headers = {
'Content-Type': 'multipart/form-data'
}
} else {
options.headers = {
'Content-Type': 'application/ld+json',
accept: 'application/ld+json'
}
}
const instance = axios.create(options);
instance.interceptors.response.use(response => {
return response;
}, error => {
return Promise.reject(error);
})
if (mockURL !== null && mockResponse !== null) {
let mock = new mockAdapter(instance);
mock.onAny(mockURL).reply(200, mockResponse)
}
return instance;
};
export default service;
Through the error dump in the node stack, I managed to see that the request headers are correct, and the JWT correctly passed through.
Do not use Axios. Just use fetch().
Next.js polyfills fetch() by default on both the client and server, so you can just use it:
In addition to fetch() on the client-side, Next.js polyfills fetch() in the Node.js environment. You can use fetch() in your server code (such as getStaticProps/getServerSideProps) without using polyfills such as isomorphic-unfetch or node-fetch.
Source.
getServerSideProps works well with axios if we return response.data
export const getServerSideProps: GetStaticProps = async ({ params }) => {
const { brandName } = params as IParams;
const brandData = await $host.get(`api/brand/${brandName}`).then(response => response.data);
return {
props: {
brand: brandData,
},
};
};
Your problem is that your async method does not return a promise.
import service from '../service'
export const getAllAccounts = async (adminToken) => {
const res = service({ jwtToken : adminToken }).get(`/accounts`);
return res;
}
In my NextJS begining I followed this tutorial , and I changed fetch to axios in this way:
export const getStaticPaths = async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
const data = await res.json();
const paths = data.map((ninja) => {
return {
params: { id: ninja.id.toString() },
};
});
return {
paths,
fallback: false,
};
};
export const getStaticProps = async (context) => {
const id = context.params.id;
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
const data = await res.json();
return {
props: { ninja: data },
};
};
I applied the change using useEffect()
useEffect(() => {
// const data = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
// const res = await data.json();
// setninja(res);
const fetchData = async () => {
const result = await axios(`https://jsonplaceholder.typicode.com/users/${id}`);
setninja(result.data);
};
fetchData();
console.log(data);
}, []);
I hope this info will be useful for you.
I Used Axios in getServerSideProps without any problems.
export const getServerSideProps: GetServerSideProps = async({
params,
res
}) => {
try {
const response = await axios.get(`/api/test`);
return {
props: {
data: response.data
},
}
} catch {
res.statusCode = 404;
return {
props: {}
};
}
};

Pass value from server side to client side in Next JS using getInitialProps

I'm building a application using nextJS.
In server/index.ts, I have :
expressApp.get('/', (req: express.Request, res: express.Response) => {
const parsedUrl = parse(req.url, true);
const { query } = parsedUrl;
let username: string | undefined;
if (process.env.STAGE !== 'local') {
username = getUsername(req)?.toString();
}
return nextApp.render(req, res, '/', {...query, loggedInUser: username});
});
and in my home page (path is '/'), I auto direct to path '/pageone' by doing:
const Home = () => {
const router = useRouter();
useEffect(() => {
router.push('/pageone', undefined, { shallow: true });
}, []);
return <PageOne />;
};
Home.getInitialProps = async (ctx: NextPageContext): Promise<{ username : string | string[] }> => {
const query = ctx.query;
return {
username: query.loggedInUser? query.loggedInUser : 'testUser'
};
};
export default Home;
I need this username variable in every page I build, how can I pass it to every page(for example pageone)?
You can attach this username variable to the res.locals which this is it's purpose.
Then access the res.locals of the getInitialProps ctx.
// server.ts
// this express middleware will attach username to res.locals.username;
expressApp.use((req, res, next) => {
res.locals = res.locals || {};
if (process.env.STAGE !== 'local') {
res.locals.username = getUsername(req)?.toString();
}
next();
});
expressApp.get('/', (req: express.Request, res: express.Response) => {
const parsedUrl = parse(req.url, true);
const {query} = parsedUrl;
return nextApp.render(req, res, '/', query);
});
Then instead of passing this value to the client, as you did, you can redirect from server side within getInitialProps.
// Home.tsx
const Home = () => {
return <PageOne />;
};
Home.getInitialProps = async ({res}: NextPageContext): Promise<{ username : string | string[] }> => {
if(res.locals.username === 'bla') {
return res.redirect(301, '/pageone');
// -----------^ will redirect at server side.
}
};
export default Home;
I've made a module that might help. đź‘Ť
Next Coke API allows typed communication between client and server:
Server:
// define API methods
const routes = {
getName: async (body) => {
return "your name is " + body.name
}
}
// export types to the client
export type AppRoutes = typeof routes
// export nextCokeHandler
export default function handler(req, res) {
return nextCokeHandler(req, res, routes)
}
Client:
// define coke client
const { coke } = nextCokeClient<AppRoutes>()
// call API methods
coke.getName({ name: "John" }).then((res) => {
console.log(res)
})

Trying to modify a data from a React Promise Response changes globally

I have created a codesandbox with a simplified version of my problem
https://codesandbox.io/s/new-react-context-api-ei92k
I get something from a fetch (in this case a user)
I then create a local copy of this user and make some changes to it
The problem: Any changes update my initial user object
Can someone tell me how this is possible? and how can I avoid this?
import React, { useState, useEffect } from "react";
import { AppSessionContext } from "./AppContext";
import Header from "./Header";
const user = {
userName: "jsmith",
firstName: "John",
lastName: "Smith",
isAdmin: true
};
const loadProfile = () => Promise.resolve(user);
function createUserWithNewName(userToUpdate) {
userToUpdate["userName"] = "Dummy";
return userToUpdate;
}
const App = () => {
const [user, setUser] = useState({});
const [Loaded, setLoaded] = useState(false);
var amendedUser = {};
useEffect(() => {
loadProfile()
.then(user => {
setUser(user);
console.log(user);
})
.then(() => {
amendedUser = createUserWithNewName(user);
console.log(amendedUser);
console.log(user);
})
.then(setLoaded(true));
}, []);
if (!Loaded) {
return "Loading";
}
return (
<AppSessionContext.Provider value={{ user }}>
<div className="App">
<Header />
</div>
</AppSessionContext.Provider>
);
};
export default App;
snippet of production code
loadTableDefault() {
fetch(defaultUrl(), {method: 'GET'})
.then(res => res.json())
.then(response => {
this.setState({
data: response,
})
return response
})
.then(response => {
this.setState({
table_data: formatResponsePretty(response),
})
})
.catch(error => console.error('Error:', error));
}
formatResponsePretty
export function formatResponsePretty(oldData) {
const newData = {
...oldData,
};
// consider re-writting the flask response to this format
const obj = { allocations: [] };
var theRemovedElement = ''
var ports = []
ports = Object.values(newData['allocations']['columns']);
ports.shift();
var dataArray = ['allocations', 'conditions', 'liquidity', 'hedging']
for (const index of dataArray) {
for (const i of newData[index]['data']) {
theRemovedElement = i.shift();
if (index === 'allocations') {
obj[index][theRemovedElement] = i
}
else {
obj[theRemovedElement] = i;
}
}
}
const rows = []
let index = 0;
Object.keys(obj).forEach(element => {
index = formatting.findIndex(x => x.name === element)
if (formatting[index] && formatting[index]['type'] === 'number') {
var new_obj = obj[element].map(function (el) {
return Number(el * formatting[index]['multiplier']).toFixed(formatting[index]['decimal']) + formatting[index]['symbol']
})
rows.push(new_obj)
}
else if (formatting[index] && formatting[index]['type'] === 'string') {
rows.push(obj[element])
}
else if (formatting[index] && formatting[index]['type'] === 'boolean') {
// there should be logic here to display true or false instead of 1 and 0
// this could be in the upload
rows.push(obj[element])
}
else {
rows.push(obj[element])
}
})
const arrOfObj = createRecords(ports, rows)
return {obj: obj, ports: ports, rows: rows, arrOfObj: arrOfObj}
}
In createUserWithNewName() you are updating the original user object and returning it.
You instead want to create a new object with all the old user properties, but with just the username changed. Thankfully, object destructuring makes this super easy:
function createUserWithNewName(oldUser) {
const newUser = {
...oldUser,
userName: 'Dummy',
};
return newUser;
}
This will copy all the properties of oldUser to a new object and then just update userName!
You're also going to want to pass user down to that second .then() as it won't currently be available in there:
.then(user => {
setUser(user);
console.log(user);
return user;
})
.then(user => {
amendedUser = createUserWithNewName(user);
console.log(user, amendedUser);
})
Update CodeSandbox link: https://codesandbox.io/s/new-react-context-api-tgqi3

Auth0 SPA TypeError: Cannot read property 'idTokenPayload' of undefined

I’m getting an error on my Gatsby+Auth0 app when trying to log in. Here is the error:
TypeError: Cannot read property 'idTokenPayload' of undefined
(anonymous function)
src/utils/auth.js:1
> 1 | import auth0 from "auth0-js"
2 | import { navigate } from "gatsby"
3 |
4 | const isBrowser = typeof window !== "undefined"
When I log in to my app it works the first time and I can see that authResult is console logging proper values, but the moment I click on any routes or refresh the page, it returns this error. I was working fine until this morning, I didn’t change anything other than some CSS styles.
Here is my auth.js file:
import auth0 from "auth0-js"
import { navigate } from "gatsby"
const isBrowser = typeof window !== "undefined"
const auth = isBrowser
? new auth0.WebAuth({
domain: process.env.AUTH0_DOMAIN,
clientID: process.env.AUTH0_CLIENTID,
redirectUri: process.env.AUTH0_CALLBACK,
responseType: "token id_token",
scope: "openid profile email",
})
: {}
const tokens = {
accessToken: false,
idToken: false,
expiresAt: false,
}
let user = {}
export const isAuthenticated = () => {
if (!isBrowser) {
return
}
return localStorage.getItem("isLoggedIn") === "true"
}
export const login = () => {
if (!isBrowser) {
return
}
auth.authorize()
}
const setSession = (cb = () => {}) => (err, authResult) => {
console.log(authResult);
localStorage.setItem("userAuthID", authResult.idTokenPayload.sub)
localStorage.setItem("userIdToken", 'Bearer '+authResult.idToken)
if (err) {
navigate("/")
cb()
return
}
if (authResult && authResult.accessToken && authResult.idToken) {
let expiresAt = authResult.expiresIn * 1000 + new Date().getTime()
tokens.accessToken = authResult.accessToken
tokens.idToken = authResult.idToken
tokens.expiresAt = expiresAt
user = authResult.idTokenPayload
localStorage.setItem("isLoggedIn", true)
navigate("/account")
cb()
}
}
export const silentAuth = callback => {
if (!isAuthenticated()) return callback()
auth.checkSession({}, setSession(callback))
}
export const handleAuthentication = () => {
if (!isBrowser) {
return
}
auth.parseHash(setSession())
}
export const getProfile = () => {
return user
}
export const logout = () => {
localStorage.setItem("isLoggedIn", false)
localStorage.removeItem("userIdToken");
auth.logout()
}
And here is my callback.js file:
import React, { Component } from 'react'
import { handleAuthentication } from "../utils/auth"
import { navigate } from 'gatsby';
export default class callback extends Component {
componentDidMount() {
handleAuthentication();
setTimeout(() => {
navigate('/account')
}, 1500);
}
render() {
return (
<div>
<div className="hero is-white is-fullheight">
<div className="hero-body">
<div className="container">
<h1 className="title is-1 is-spaced has-text-centered logo-text">Fottom</h1>
</div>
</div>
</div>
</div>
)
}
}
Can some please help me find what is missing here? Thanks!
Your problem here is that you are trying to access the property idTokenPayload of authResult, without checking before if authResult is null. In your setSession() function, in case of error, you are accessing authResult. You should only set the localStorage items when the login was successful.
Try this code:
const setSession = (cb = () => {}) => (err, authResult) => {
console.log(authResult);
if (err) {
navigate("/")
cb()
return
}
if (authResult && authResult.accessToken && authResult.idToken) {
localStorage.setItem("userAuthID", authResult.idTokenPayload.sub) // moved after error control
localStorage.setItem("userIdToken", 'Bearer '+authResult.idToken) // moved after error control
let expiresAt = authResult.expiresIn * 1000 + new Date().getTime()
tokens.accessToken = authResult.accessToken
tokens.idToken = authResult.idToken
tokens.expiresAt = expiresAt
user = authResult.idTokenPayload
localStorage.setItem("isLoggedIn", true)
navigate("/account")
cb()
}
}
I agree with #mhSangar that you should probably only attempt to access the payload AFTER ensuring that it's not null.
But I think there's also another layer to the issue that you're experiencing. You mentioned that everything works fine until you refresh, which I'm assuming is when the token refresh is attempted. My guess is that the issue lies within the checkSession method.
Currently, it's being invoked like this:
auth.checkSession({}, setSession(callback))
You have to pass a config as the first argument to that method (not just an empty object). When I invoke it in my app, I pass the following args:
{
audience,
callbackURL,
clientID,
domain
}
Try passing those arguments to auth.checkSession and see if that fixes it.
i.e.
export const silentAuth = callback => {
if (!isAuthenticated()) return callback()
auth.checkSession(
{
domain: process.env.AUTH0_DOMAIN,
clientID: process.env.AUTH0_CLIENTID,
redirectUri: process.env.AUTH0_CALLBACK,
audience: YOUR_AUDIENCE (looks like AUTH0_DOMAIN/api/v2/)
},
setSession(callback)
)
}

Resources