How to properly store access token in memory react - javascript

I'm searching for a clean way to store my access token in React.
First thing: I don't want to use local storage. I don't need to make my access token persistent since I can always refresh it.
I also excluded a cookie since I want to prevent CSRF attacks. By storing the access token only in memory, in fact, the page needs to be loaded to get the token and authenticate requests (refresh token can be used only to refresh)
I thought of using redux/context, however, the function calling the API is not a child of a component so I can't access the token from that. Furthermore, I don't want to pass the token as a parameter, since I want to keep decoupled the HTTP logic. Maybe there is a clean way to use it?
After a bit of research, I found that using a global variable is a working "cheat" to obtain what I want. However, I was guessing if there was a clearer way to get the same result.
//token.js
const access_token= "";
export const setAccessToken(token){
access_token=token;
}
export const getAccessToken(){
return access_token;
}
//NOTICE:
// -ON LOGIN/REFRESH: I set the access token
// -ON API CALLS: I get the access token and I add it to the header
api.js
const baseURL= "http://my_base_url";
const generateApiInterface = ()=>{
let headers : any= {
};
token=getAccessToken(); //HERE I NEED TO RETRIEVE MY ACCESS TOKEN
if(token){
headers={
'Authorization': 'Token '+ token,
...headers //append other basic proprieties
}
}
return axios.create({
baseURL: baseURL,
headers: headers
});
}
const api = generateApiInterface();

The way you're doing it is preferable to keep anything in memory. If this is server side, then you'd want to make sure you delete the token once you're done, but if not then the current approach is desirable. Note, you're using a const, you'd want to change that to a let. Another idea would be to use session storage, but then that brings the idea of XSS.
However, React provide a way to have "global" state - this would be to use a provider and context. Here's an example:
// provider.tsx
import React, { useContext, createContext, FC, useState } from 'react'
type AccessTokenContext = [string, React.Dispatch<React.SetStateAction<string>>]
const AccessTokenProvider: FC = (props) => {
const [accessToken, setAccessToken] = useState<string>(null)
return <AccessToken.Provider value={[accessToken, setAccessToken]} {...props} />
}
const AccessToken = createContext<AccessTokenContext>(null)
const useAccessToken = (): AccessTokenContext => useContext<AccessTokenContext>(AccessToken)
export { AccessTokenProvider, useAccessToken }
You'd have to wrap your app container in AccessTokenProvider:
// index.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import { AccessTokenProvider } from './providers/AccessTokenProvider'
ReactDOM.render(
<AccessTokenProvider>
<App />
</AccessTokenProvider>,
document.getElementById('app')
)
Then you can then use the hook useAccessToken in App and any children of App. Of course, this provider doesn't have to be root level, but it's easiest to include it here.
// app.tsx
import React, { FC } from 'react'
const App: FC = props => {
const [accessToken, setAccessToken] = useAccessToken()
return <div>{/* content */}</div>
}
export default App

Related

Retrieve Route Parameters in any Component

Suppose my URL looks something like this:
/blog/[post_id]/something
What is the recommended way to pass $post_id down to any component anywhere in the tree?
I know how to retrieve route parameters using getInitialProps but passing the values down is always giving me a hard time.
For pages I could technically use React Contexts although this seems a bit oversized for such a trivial use case.
For layouts I am honestly completely lost because pages are children of layouts and the return value of getInitialProps is passed to the page and not the layout.
My components could make use of useRouter but this requires useEffect and would also make my component depend on the route itself...
Any advice would be welcome (:
My components could make use of useRouter but this requires useEffect and would also make my component depend on the route itself...
useRouter seems like the obvious solution here. I'm not exactly understanding your concerns regarding the component depending on the route. I guess it does make the Layout less flexible since it needs to know that the post id is stored in the post_id query variable. But I would do it anyways :) It gives you a nice and simple way to access the query variables which can be used in a Layout that's outside of your BlogPost or in a deeply-nested component that you use inside the BlogPost.
Using the per-page layouts approach:
/components/Layout
import { useRouter } from "next/router";
import { ReactNode } from "react";
export default function Layout({ children }: { children: ReactNode }) {
const router = useRouter();
return (
<div>
<h3>You are viewing post id #{router.query.post_id}</h3>
{children}
</div>
);
}
/pages/blog/[post_id].jsx
import Layout from '../../components/Layout';
export default function BlogPost() {
return <div>Hello World</div>
}
BlogPost.getLayout = function getLayout(page) {
return (
<Layout>
{page}
</Layout>
)
}
/pages/_app.tsx (to support per-page layouts, copied from docs)
export default function MyApp({ Component, pageProps }) {
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout || ((page) => page)
return getLayout(<Component {...pageProps} />)
}
I think the easiest and the cleanest way is to use window.location.pathname. this will give you the part after the domain name. for example for
http://localhost:3001/blog/[post_id]/something
you will get /blog/[post_id]/something
const pathname=window.location.pathname
const splittedPathname=pathname.split("/") // ['', 'blog', '[post_id]', 'something']
const dynamicId=splittedPathname[2]
you can run above code in useEffect and set a state. or you could write a hook and use it in the components that under dynamicId components
import React, { useState, useEffect } from "react";
const usePathname = () => {
const [postId, setPostId] = useState("");
useEffect(() => {
const pathname = window.location.pathname;
const splittedPathname = pathname.split("/");
const dynamicId = splittedPathname[2];
setPostId(dynamicId);
}, []);
return { postId };
};
export default usePathname;
If you are looking for client side rendering, useRouter is the best way to go. If you are looking for SSR or SSG, you should rather use getStaticProps or getServerSideProps.

Use nextjs routing outside react hooks in regular javascript

I have a NextJS app. I want to add a function that can redirect to any page using nextjs routing.
For example, after finishing signup I want to redirect to a certain page.
If I create this function (reusable everywhere) :
import { useRouter } from 'next/router'
const goTo = (href) => {
const router = useRouter()
router.push(href)
}
I want to add this to my signup Logic, the problem is that I break React Hooks rules :
react-dom.development.js?ac89:14906 Uncaught (in promise) Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
And effectively useRouter is a React Hook, and I'm trying to call it outside a React function, as stipulated here https://reactjs.org/docs/hooks-rules.html it will not work.
How can I then have a routing solution for NextJS to be callable in regular Javascript ?
So If you're using purely React.js (without Next.js on top of it), you can simple do it this way:
Import React from 'react'
export const handleRoutes = (url) => {
const history = React.useHistory()
return history.push(url)
}
Then You'd import this regular js function in any of your react files, like this:
import { handleRoutes } from 'fileName.js'
<button onClick={() => handleRoutes("/routeToBeRedirectedTo")}> Click to redirect </button>
However when using Nextjs I'm not sure that the above way would work. My personal method would be simply implementing this function (in a utility.js file):
export const handleRedirect = (router, url) => {
return router.push(url)}
Then just importing the function & useRouter hook in the file you want:
import { handleRedirect } from "./utility.js"
import { useRouter } from "next/router"
const router = useRouter
Then inside your JSX return statement:
<button onClick={() => handleRedirect(router, "/routeToBeRedirectedTo")}> Click to redirect </button>
And if it's a redirect after sign in/sign up, just simply useEffect like so:
// depends if you're storing your user credentials in your local storage or cookies, this example below would be if your User credentials are stored in localstorage
const user = JSON.parse(localstorage.getItem("user"))
UseEffect(() => {
if (user) return handleRedirect(router, "/routeToBeRedirectedTo")
}, [user])

how to listen for route change in react-router-dom v6

am trying to migrate the old react router dom code to v6 and I want to know how to listen for route change, I am now using useHistory
const history = useHistory()
//then
history.listen(...)
I did read the new docs and I did find that useHistory was changed to useNavigate
const navigate = useNavigate()
//then
navigate.listen(...) // listen is not a function
can you please help me find a way to listen to the route change in v6
// This is a React Router v6 app
import { useNavigate } from "react-router-dom";
function App() {
let navigate = useNavigate();
function handleClick() {
navigate("/home");
}
return (
<div>
<button onClick={handleClick}>go home</button>
</div>
);
}
From documentation (https://reactrouter.com/en/main/hooks/use-location), use this hook
let location = useLocation();
React.useEffect(() => {
ga('send', 'pageview');
}, [location]);
The navigate function is a function, not an object like the older react-router-dom version 5's history object.
You can still create a custom history object but you'll need to create a custom router to use it. This allows you to import your history object and create listeners.
Create a custom router example, use one of the higher-level routers as an example for how they manage the location and state, i.e. BrowserRouter:
const CustomRouter = ({ history, ...props }) => {
const [state, setState] = useState({
action: history.action,
location: history.location
});
useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
{...props}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
};
In your code create the custom history object for use by your new custom router and other components. Ensure you have history#5 installed as a project dependency. This is the same version used by RRDv6. If you need to install it run npm i history#5 to add it to the project's dependencies.
const history = createBrowserHistory();
export default history;
Use your router and pass your history object to it.
import CustomRouter from '../CustomRouter';
import history from '../myHistory';
...
<CustomRouter history={history}>
....
</CustomRouter>
In a component you want to listen to location changes on, import your history object and invoke the listen callback as you did previously.
import history from '../myHistory';
...
useEffect(() => {
const unlisten = history.listen((location, action) => {
// ... logic
});
return unlisten;
}, []);
If you want, you may be able to also create your own custom useHistory hook that simply returns your history object.
Update
react-router-dom has started exporting a HistoryRouter for a use case like this. Instead of importing the low-level Router and implementing the internal logic you import unstable_HistoryRouter as HistoryRouter and pass your custom history object (memory, hash, etc).
import { unstable_HistoryRouter as HistoryRouter } from "react-router-dom";
import history from "../myHistory";
...
<HistoryRouter history={history}>
....
</HistoryRouter>
Notes on RRDv6.4+
If you are using RRDv6.4+ and not using the Data routers the good-ish news is that unstable_HistoryRouter is still being exported through at least RRDv6.8.0. You can follow along the filed issue in the repo here.
If you are using the Data routers then the new "unstable" method is to use an attached navigate function from the router object directly.
Example:
import { createBrowserRouter } from 'react-router-dom';
// If you need to navigate externally, instead of history.push you can do:
router.navigate('/path');
// And instead of history.replace you can do:
router.navigate('/path', { replace: true });
// And instead of history.listen you can:
router.subscribe((state) => console.log('new state', state));
I've had mixed results with using the history.listen solution between versions 6.4 and 6.8, so probably best to keep an eye on the linked issue for whatever the RRD maintainers say is the current "unstable" method of accessing the "history".
To add to the accepted answer (can't comment, not enough rep points), subscribing to the history through a useEffect with location.pathname in the dependency array won't work if the navigation unmounts the component you're attempting to call the useEffect from.
If you need to react to a change in the route due to back button specifically:
In react-router-dom v6.8.0 or even earlier, trying to attach a listener to the history, will throw an error: A history only accepts one active listener.
I learnt that react-router-dom seems to introduce a lot of changes between the minor versions as well, so you should take words like unsafe and unstable , like in unstable_HistoryRouter especially serious. They will break sooner or later, if you're not very lucky.
In my case I had to upgrade to get the reintroduced optional route params, and the UNSAFE_NavigationContext my former colleague decided to use, didn't work anymore.
So here's a high level approach, that allows you to listen to the actions on the Router's history stack, without attaching another listener to the router yourself. Which is fine, as it already has one listener by default, and it's just not exposed, but the actions derived from it are, which is enough.
In the following example we are reacting to changes in location and for each change, we check if it was due to a POP action, thats e.g. triggered when the browser's back button is used, and then execute whatever..
import { useEffect } from "react";
import {
Location,
NavigationType,
useLocation,
useNavigationType,
} from "react-router-dom";
export const useBackListener = (callback: () => void) => {
const location: Location = useLocation();
const navType: NavigationType = useNavigationType();
useEffect(() => {
if (navType === "POP" && location.key !== "default") {
if (someCondition === true) callback();
else {
doSomethingElse();
}
}
}, [location]);
};

Code for passing user information from azureAD to app.js

I am trying to introduce the Single-Sign-On (SSO) feature to my React app and I want to use the user information from my company for the SSO verification. For that, I registered my application on the Azure website to acquire the clientID. I have found an article that shows how to make use of React AAD MSAL package to enable user login through a popup method: https://www.npmjs.com/package/react-aad-msal .
I create an authProvider.js file with the following code:
// authProvider.js
import { MsalAuthProvider, LoginType } from 'react-aad-msal';
// Msal Configurations
const config = {
auth: {
authority: 'https://login.microsoftonline.com/common',
clientId: 'XXXXXXXXXXXXXXXXXXXXXXX',
redirectUri: 'localhost:3000'
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: true
}
};
// Authentication Parameters
const authenticationParameters = {
scopes: [
'User.Read'
]
}
// Options
const options = {
loginType: LoginType.Popup,
tokenRefreshUri: window.location.origin + '/auth.html'
}
export const authProvider = new MsalAuthProvider(config, authenticationParameters, options)
and my index.js file looks like this:
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { AzureAD } from 'react-aad-msal';
import App from './App';
import { authProvider } from './authProvider';
ReactDOM.render(
<BrowserRouter>
<AzureAD provider={authProvider} forceLogin={true}>
<App />
</AzureAD>
<BrowserRouter>,
document.getElementById('root'),
);
This code worked and allowed me to authenticate users on the sign in and then redirected them to the redirect URL. I now would like to pass user information such as name, surname, email address and profile picture to the App component.
I was not able to find resources online that would help me do it and the documentation on the npm website does not have examples of that being done. Could anyone provide working code examples of how to do what I want? (All my files are in js and I can not use any code examples provided by Microsoft as they use tsx files and I don't want to change all my files to TypeScript).
Perhaps there are some children or property functions in MsalAuthProvider that I can call but I could not find documentation on that function either. I am also not sure of where to call these functions in index.js so code examples would be very helpful. Thanks
You could get user information with myMSALObj.getAccount() using MSAL.js.
const myMSALObj = new Msal.UserAgentApplication(msalConfig);
function signIn() {
myMSALObj.loginPopup(loginRequest)
.then(loginResponse => {
console.log('id_token acquired at: ' + new Date().toString());
console.log(loginResponse);
if (myMSALObj.getAccount()) {
showWelcomeMessage(myMSALObj.getAccount());
}
}).catch(error => {
console.log(error);
});
}
For more details about the code sample, see here.
Also you could get access token in your code with react-aad-msal. Some information of the user such as username and email will be shown after decoding the token.
import jwtDecode from 'jwt-decode';
const claims = jwtDecode('base64 encoded token received from Azure AD');

Relay's Inject Network Layer Not Being Recognized as a Function in a React App

I am following along a Lynda.com tutorial called "Building and Deploying a Full-Stack React Application", and in the chapter "Injecting the Relay Network Layer" there is in index.js, an attempt to set up a network layer, and the program compiles successfully but I'm receiving the following error in the browser:
TypeError: __WEBPACK_IMPORTED_MODULE_4_react_relay___default.a.injectNetworkLayer is not a function
Any ideas?
I'd appreciate it,
CM
(My index.js file)
import React from 'react'
import ReactDOM from 'react-dom'
import {Router, browserHistory, applyRouterMiddleware} from 'react-router'
import Routes from './routes'
import Relay from 'react-relay'
import useRelay from 'react-router-relay'
import {RelayNetworkLayer, urlMiddleware} from 'react-relay-network-layer'
import {relayApi} from './config/endpoints'
import auth from './utils/auth'
const createHeaders = () => {
let idToken = auth.getToken()
if (idToken) {
return {
'Authorization': `Bearer ${idToken}`
}
} else {
return {}
}
}
Relay.injectNetworkLayer(
new RelayNetworkLayer([
urlMiddleware({url: (req) => relayApi,}),
next => req => {
req.headers = {
...req.headers,
...createHeaders()
}
return next(req)
},
],{disableBatchQuery: true})
)
ReactDOM.render(
<Router
environment={Relay.Store}
render={applyRouterMiddleware(useRelay)}
history={browserHistory}
routes={Routes}
/>,
document.getElementById('root')
)
You are probably not using the right version of Relay, but its just a guess. Check if the tutorial specifies any version and check which one you are using.
A lot of things changed in the last version of Relay: Relay-Modern. You might want to look into that, its way more convenient and efficient than Relay-Classic.
Also there are easier ways to combine a router with relay. Create your Relay Environment directly above or below your router, depending on if you need to get routes out of your db. But I guess you just need to get through the tutorial.

Categories

Resources