React render run before fetch method receives token from server - reactjs

I have a JS file within my React application, which connects to the server, sends username and password, receives an oauth token from the server and stores the token in the local storage.
However before the token received by react, the react sends the next request before token stored in the local storage. Which leads to 401 unauthorized access.
AuthService.js
login(username, password) {
console.log(username);
return this.fetch(`${this.domain}/api/AuthAPI/getCredentials`, {
headers: {
'Access-Control-Allow-Origin': "*"
}
}).then(res => {
this.fetch(`${this.domain}/Token`, {
method: 'POST',
body: 'grant_type=password&username=' + res[0]
}).then(response => {
var date_token_issue = new Date();
this.setToken(response.access_token,response.expires_in, date_token_issue) // Setting the token in localStorage
return Promise.resolve(response);
})
})
}
setToken(idToken,expires, date_token_issue ) {
localStorage.setItem('id_token', idToken)
localStorage.setItem('expires', expires)
localStorage.setItem('date_token_issue', date_token_issue)
}
SignIn.jsx
import React, { Component } from 'react'
import AuthService from '../comonents/AuthService';
import Orders from '../../app/orders/orders'
import { Redirect, Switch, Route} from "react-router-dom";
export default function SignIn(AuthComponent){
const Auth = new AuthService('http://localhost:53050');
return class AuthWrapped extends Component {
constructor() {
super();
this.state = {
user: null,
loggedIn: false
}
}
async componentDidMount() {
if (!Auth.loggedIn()) {
const promise = await Auth.login('m.dawaina', 'm.dawaina');
console.log(promise)
this.setState({loggedIn: true});
}
else {
try {
this.setState({loggedIn: true})
const profile = Auth.getProfile()
this.setState({
user: profile
})
}
catch(err){
Auth.logout()
//this.props.history.replace('/login')
}
}
}
render() {
if (this.state.loggedIn) {
return (
<div>
<Redirect to='/orders'/>
<Switch>
<Route path="/orders" component={Orders} />
</Switch>
</div>
)
}
else {
return (
<AuthComponent history={this.props.history} user={this.state.user} />
)
}
}
}
}
I need a way to force react wait for the JS receives the token and stores it in the local storage, and prevent react sending the next request until it finds the token stored in the local storage.

login(username, password) {
console.log(username);
return this.fetch(`${this.domain}/api/AuthAPI/getCredentials`, {
headers: {
'Access-Control-Allow-Origin': "*"
}
}).then(res => {
// Add a return here
return this.fetch(`${this.domain}/Token`, {
method: 'POST',
body: 'grant_type=password&username=' + res[0]
}).then(response => {
var date_token_issue = new Date();
this.setToken(response.access_token,response.expires_in, date_token_issue) // Setting the token in localStorage
return Promise.resolve(response);
})
})
You need to add a return to the then function so that await will wait for the inner promise to resolve.

Related

How do I persist my next-auth user session? so i could use the ID provided to fetch data in other routes

What I want to achieve here is, whenever a user logs in, I want to store the data returned because the data holds an ID that I would use to fetch data in other routes.
When a user successfully logs in, he would be redirected to the /home route and the ID gotten from the session would be used to fetch data. Everything works fine initially, but if I refresh the home page, the user becomes null.
This is what my [...nextauth].js looks like.
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import axios from "axios";
export default NextAuth({
providers: [
CredentialsProvider({
name: "credentials",
credentials: {
username: { label: "Username", type: "text", placeholder: "justin" },
password: {label: "Password",type: "password",placeholder: "******"},
},
async authorize(credentials, req) {
const url = req.body.callbackUrl.split("/auth")[0];
const { username, password } = credentials;
const user = await axios({
url: `${url}/api/user/login`,
method: "POST",
data: {
username: username,
password: password,
},
"content-type": "application/json",
})
.then((res) => {
return res.data;
})
.catch((err) => {
if (err.response.data) {
throw new Error(err.response.data);
} else {
return null;
}
return null;
});
return user;
},
}),
],
callbacks: {
jwt: ({ token, user }) => {
if (user) {
token.user = user;
}
return token;
},
session: ({ session, token }) => {
if (token) {
session.user = token.user;
}
return session;
},
},
pages: {
signIn: "/auth/login",
newUser: "/auth/register",
},
});
and this is what my /home route looks like
import Card from "#/components/card/Card";
import React, { useEffect, useState } from "react";
import styles from "./home.module.css";
import { Ubuntu } from "#next/font/google";
import { useSession } from "next-auth/react";
import { useDispatch, useSelector } from "react-redux";
const ubuntu = Ubuntu({ weight: "500", subsets: ["cyrillic"] });
const getData = async (id) => {
const res = await fetch({
url: "http://localhost:3000/api/note/getall",
method: "POST",
"content-type": "application/json",
data: {
id: id,
},
});
if (!res.ok) {
console.log(id);
throw new Error("Unable to fetch");
} else {
return res.json();
console.log(res);
}
};
function home() {
const colors = ["#E9F5FC", "#FFF5E1", "#FFE9F3", "#F3F5F7"];
const random = Math.floor(Math.random() * 5);
const rc = colors[random];
const [pop, setPop] = useState("none");
const { user } = useSelector((state) => state.user);
const getDataa = async () => {
console.log(user)
const data = await getData(user._id);
console.log(data);
};
useEffect(() => {
if (user) {
alert(user)
}
}, []);
return (
<div className={styles.home}>
<header>
<h3 className={ubuntu.className}>
Hello, <br /> {user?.username}!
</h3>
<input type="text" placeholder="search" />
</header>
<div className={styles.nav}>
<h1 className={ubuntu.className}>Notes</h1>
</div>
<div className={styles.section}>
<div className={styles.inner}>
{/* {data &&
data.map((e) => (
<Card
rawData={e}
color={colors[Math.floor(Math.random() * colors.length)]}
/>
))} */}
</div>
</div>
<div className="new"></div>
</div>
);
}
export default home;
Add this component to your App.js file :
function Auth({ children }) {
const router = useRouter();
const { status } = useSession({
required: true,
onUnauthenticated() {
router.push("/sign-in");
},
});
if (status === "loading") {
return <div>Loading ...</div>;
}
return children;
}
Now in your App function instead of returning <Component {...pageProps} /> you check first if the component has auth property, so you wrapp it with <Auth> to ensure that every component that requires session will only mount when the session finishes loading (that's why the user is null because the session is still loading)
{
Component.auth ? (
<Auth>
<Component {...pageProps} />
</Auth>
) : (
<Component {...pageProps} />
);
}
finally you add .auth = {} to every page in whitch you want the session to be defined (Home in your case)
const Home = () => {
//....
}
Home.auth = {};
This also helps to redirect user to /sign-in page if the session is expired
This code seems like it would create a problem / race-condition since you're mixing two different async promise handling styles:
const user = await axios({
url: `${url}/api/user/login`,
method: "POST",
data: {
username: username,
password: password,
},
"content-type": "application/json",
})
.then((res) => {
return res.data;
})
.catch((err) => {
if (err.response.data) {
throw new Error(err.response.data);
} else {
return null;
}
return null;
});
return user;
It should either be this:
try {
const user = await axios({
url: `${url}/api/user/login`,
method: "POST",
data: {
username: username,
password: password,
},
"content-type": "application/json",
});
return user.data;
} catch (err) {
if (err.response.data) {
throw new Error(err.response.data);
} else {
return null;
}
}
Or this:
axios({
url: `${url}/api/user/login`,
method: "POST",
data: {
username: username,
password: password,
},
"content-type": "application/json",
}).then((res) => {
return res.data;
}).catch((err) => {
if (err.response.data) {
throw new Error(err.response.data);
} else {
return null;
}
return null;
});

Method returning undefined even though fetch succeeds?

I have two components, Client and App, and a fetch function. App is the child component of Client. I want to update Client's state using the return value from the method App calls. However, Client's state response is undefined after the fetch. I'm not sure why this code does not work.
import React, { Component } from 'react';
import './App.css';
function post(user, token, data){
console.log('fetching...')
fetch(`/project`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic '+ btoa(user+':'+token),
},
body: JSON.stringify(data)
}).then(r => {
if (!r.ok)
throw Error(r.status);
r.json().then(r => {return(r)});
}).catch(error => {throw Error(error)})
}
class Client extends Component {
constructor(props) {
super(props);
this.state = {
user: '',
token: '111',
project: {'project':'demo'},
response: {},
};
this.updateState = this.updateState.bind(this);
};
updateState(){
const { user, token, project } = this.state;
post(user, token, project).then(text => this.setState({ response: text
}));
}
render() {
return (
<App updateState={this.updateState}/>
)
}
}
class App extends Component {
render() {
return (
<div className="App">
<button onClick={ () => {
this.props.updateState()} }>Fetch Project</button>
</div>
);
}
}
EDIT: I changed my post() to this and it works :)
async function post(user, token, data){
console.log('fetching...')
const response = await fetch(`/project`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic '+ btoa(user+':'+token),
},
body: JSON.stringify(data)
}).catch(error => {throw Error(error)});
if(!response.ok)
throw Error(response.status);
const obj = await response.json();
return(obj);
}
If you are working with promises, you can do something like this.
import React, { Component } from "react";
async function post() {
// fetch //
return await fetch("https://hipsum.co/api/?type=hipster-centric");
}
class Client extends Component {
constructor(props) {
super(props);
this.state = {
response: "12"
};
this.updateState = this.updateState.bind(this);
}
async updateState(res) {
const text = await res().then(res => res.text());
this.setState({ response: text });
}
render() {
return (
<>
{this.state.response}
<App updateState={this.updateState} />
</>
);
}
}
class App extends Component {
render() {
return (
<div>
<button
onClick={() => {
this.props.updateState(post);
}}
>
Fetch
</button>
</div>
);
}
}
export default Client;
sandbox
It will be nice to know all the code for the fetch function but I think the problem is mostly here:
this.props.updateState(post())
That call is synchronous and the fetching process isn't. You need a better approach with await or promises or a callback.

API login React Not Connecting

enter image description hereI'm trying to login/register a new user, however, it won't let me and I have no idea how to get it fixed. What should I do about this? I tried to find out on how to do this but it is kind of getting complicated for me and I have no idea. It's like the code doesn't want to work.
enter code here
import React, { useEffect, useState } from "react";
import LoginForm from "../LoginForm";
import Auth from "../../utils/Auth";
import { useLocation, useHistory } from "react-router";
//Uses the Auth methods to actually login with the LoginForm Component.
function Login() {
let location = useLocation();
let history = useHistory();
const [redirectToReferrer, setRedirectToReferrer] = useState(false);
useEffect(() => {
const { from } = location.state || { from: { pathname: "/protected" } };
if (redirectToReferrer) {
history.push(from);
}
}, [redirectToReferrer, history, location.state]);
/* We need to POST to the API the users info,
This will get passed down as a prop to the LoginForm */
const login = (data) => {
console.log("Logging in " + JSON.stringify(data));
//fetch('api/users/login', { is the error
fetch("api/users/login", {
method: "POST",
body: JSON.stringify(data),
credentials: "include",
headers: {
"Content-Type": "application/json",
},
})
.then((response) => {
if (response.status === 200) {
//All good
Auth.authenticate(() => {
//Update the boolean and take off the cuffs
setRedirectToReferrer(true);
console.log(`Response in login ${JSON.stringify(response)}`);
});
}
})
.catch((err) => {
// No beuno, kick them
console.log("Error logging in.", err);
});
};
return (
<div>
<LoginForm onLogin={login} />
</div>
);
}
export default Login;

React - Send cookie to Express

I am getting a token to access an API endpoint and I want to send this token to my server-side app (expressJS) to retreive the data.
I have the following for my react app:
export default class Account extends React.Component {
constructor() {
super();
this.state = {
token: null,
response: {
}
};
this.getCurrentlyPlaying = this.getCurrentlyPlaying.bind(this);
}
componentDidMount() {
// Set token
let _token = hash.access_token;
if (_token) {
this.setState({
token: _token
});
const cookies = new Cookies();
cookies.set('token', _token, { path: '/' });
console.log(cookies.get('token'));
this.getCurrentlyPlaying(_token);
}
}
getCurrentlyPlaying() {
fetch(`http://localhost:3001/account`)
.then(res => res.json())
.then(data => {
this.setState ({
response: data
})
console.log(data);
});
}
render() {
if (this.state.response[0].is_playing === true) {
return (
<p> Something is playing</p>
);
}
else {
return (
<p> Nothing is playing</p>
);
}
}
}
In my express app, I have the cookie being gotten but I'm not sure if it actually is getting the cookie created by the react app:
router.get('/account', (req, res) => {
const config = {
headers: {
'Authorization': `Bearer ${req.session.token}`
}
};
fetch(`${CONFIG.spotifyUrl}/me/player/currently-playing `, config)
.then(html => html.json())
.then(json => {
res.json(json);
});
});
module.exports = router;
Can someone tell me where I'm going wrong please?
To parse cookies in backend with express, a good choice is to use the https://github.com/expressjs/cookie-parser middleware.
Provided you are using setup something similar to below
const cookieParser = require('cookie-parser');
app.use(cookieParser());
Every Request object on server will have cookies information in the req.cookies property. So in your case it should be req.cookies.token

React authentication with JWT

I am trying to add an authentication system to my application in React / Laravel. For that I make a request for the theory recovers a token as on Passport. The problem is that it returns me a token undefined ... Yet when I look at the console of my browser I see in preview the token in question ...
Can someone please guide me to solve this problem?
Here is the code of my Auth service
import decode from 'jwt-decode';
export default class AuthService {
// Initializing important variables
constructor(domain) {
this.domain = domain || 'http://127.0.0.1:8000' // API server
domain
this.fetch = this.fetch.bind(this) // React binding stuff
this.login = this.login.bind(this)
this.getProfile = this.getProfile.bind(this)
}
login(username, password) {
// Get a token from api server using the fetch api
return this.fetch(`${this.domain}/oauth/token`, {
method: 'POST',
body: JSON.stringify({
username,
password,
grant_type: "password",
client_id:"2",
client_secret : "Wu07Aqy9pU5pLO9ooTsqYDBpOdzGwrhvw5DahcEo"
})
}).then(res => {
this.setToken(res.token) // Setting the token in localStorage
return Promise.resolve(res);
})
}
loggedIn() {
// Checks if there is a saved token and it's still valid
const token = this.getToken() // GEtting token from localstorage
return !!token && !this.isTokenExpired(token) // handwaiving here
}
isTokenExpired(token) {
try {
const decoded = decode(token);
if (decoded.exp < Date.now() / 1000) { // Checking if token is
expired. N
return true;
}
else
return false;
}
catch (err) {
return false;
}
}
setToken(token) {
// Saves user token to localStorage
localStorage.setItem('access_token', token)
}
getToken() {
// Retrieves the user token from localStorage
return localStorage.getItem('access_token')
}
logout() {
// Clear user token and profile data from localStorage
localStorage.removeItem('access_token');
}
getProfile() {
// Using jwt-decode npm package to decode the token
return decode(this.getToken());
}
fetch(url, options) {
// performs api calls sending the required authentication headers
const headers = {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
// Setting Authorization header
// Authorization: Bearer xxxxxxx.xxxxxxxx.xxxxxx
if (this.loggedIn()) {
headers['Authorization'] = 'Bearer ' + this.getToken()
}
return fetch(url, {
headers,
...options
})
.then(this._checkStatus)
.then(response => response.json())
}
_checkStatus(response) {
// raises an error in case response status is not a success
if (response.status >= 200 && response.status < 300) { // Success
status lies between 200 to 300
return response
} else {
var error = new Error(response.statusText)
error.response = response
throw error
}
}
}
Here of my form
import React, { Component } from 'react';
import AuthService from './AuthService';
import { Router, Route, Switch, Link } from 'react-router-dom'
class Login extends Component {
constructor(){
super();
this.handleChange = this.handleChange.bind(this);
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.Auth = new AuthService();
}
handleFormSubmit(e){
e.preventDefault();
this.Auth.login(this.state.username,this.state.password)
.then(res =>{
this.props.history.replace('/Localisations');
})
.catch(err =>{
alert(err);
})
}
componentWillMount(){
if(this.Auth.loggedIn())
this.props.history.replace('/');
}
render() {
return (
<div className="center">
<div className="card">
<h1>Login</h1>
<form onSubmit={this.handleFormSubmit}>
<input
className="form-item"
placeholder="Username goes here..."
name="username"
type="text"
onChange={this.handleChange}
/>
<input
className="form-item"
placeholder="Password goes here..."
name="password"
type="password"
onChange={this.handleChange}
/>
<input
className="form-submit"
value="Submit"
type="submit"
/>
</form>
</div>
</div>
);
}
handleChange(e){
this.setState(
{
[e.target.name]: e.target.value
}
)
}
}
export default Login;
And here of of my with Auth
import React, { Component } from 'react';
import AuthService from './AuthService';
export default function withAuth(AuthComponent) {
const Auth = new AuthService('http://127.0.0.1:8000');
return class AuthWrapped extends Component {
constructor() {
super();
this.state = {
user: null
}
}
componentWillMount() {
if (!Auth.loggedIn()) {
this.props.history.push('/Localisations')
}
else {
try {
const profile = Auth.getProfile()
this.setState({
user: profile
})
}
catch(err){
Auth.logout()
this.props.history.push('/')
}
}
}
render() {
if (this.state.user) {
return (
<AuthComponent history={this.props.history} user= .
{this.state.user} />
)
}
else {
return null
}
}
};
}

Resources