React JS - How to authenticate credentials via a fetch statement - javascript

My goal is to create a React JS login page that runs off a json Rest service. In Postman, when I enter the URL for the service, set it to run as POST and enter the following JSON into the body:
{username: "myUserName", password: "myPassword"}
...a token is returned. So in my fetch clause, I'm using JSON.stringify to pass the username and password to the server.
I'm new to using Fetch with react, So my question is, how do I get started in authenticating various users, just using react JS with fetch only? I assume, I'm to write my logic within the second then of my Fetch clause?
Currently, my page accepts any credentials and routes the user to a landing page upon clicking the submit button. I have a function containing fetch and now calling the fetch function once the onSubmit button is clicked, which now grabs the token.
This is my code:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import './Login.css';
import { withRouter } from 'react-router-dom';
class Login extends Component {
constructor() {
super();
this.state = {
data: [],
username: "",
password: "",
token: "",
};
} //end constructor
componentWillMount() {
}
componentDidMount() {
this.fetchData();
}
fetchData() {
fetch('http://theapi/api/auth', {
method: 'POST',
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify({
username: 'myUserName',
password: 'myPassword',
Authorization: 'TheReturnedToken',
})
}) /*end fetch */
.then(results => results.json())
.then(data => this.setState({ data: data })
)
}
//request the token
requestAccessToken(data) {
const loginInfo = '${data}&grant_type=password';
return fetch('${API_URL}Token', {
method: 'POST',
headers: new Headers({
'Content-Type': 'application/json',
}),
body: loginInfo,
})
.then((response) => response.json());
}
//authenticate request
requestUserInfo(token) {
return fetch('${API_URL}api/participant/userinfo', {
method: 'GET',
headers: new Headers({
Authorization: 'Bearer ${token}',
}),
})
.then((response) => response.json());
}
change = (e) => {
this.setState({
[e.target.name]: e.target.value
});
}; //end change
onSubmit = (e) =>{
this.fetchData();
e.preventDefault();
//console.log(this.state);
this.setState({
username: "",
password: "",
});
this.props.history.push('/landing');
};
render() {
console.log(this.state.data);
return (
<div>
<div className="loginContainer">
<h2>Member Login</h2>
<form>
<input
id="username"
name="username"
placeholder="User Name"
value={this.state.username}
onChange={e => this.change(e) }
className="form-control"
/> <br />
<input
id="password"
name="password"
type="password"
placeholder="Password"
value={this.state.password}
onChange={e => this.change(e) }
className="form-control"
/> <br />
<button onClick={e => this.onSubmit(e)} className="btn btn-primary">Submit</button>
</form>
</div>
</div>
);
}
}
export default withRouter(Login);
How do I get started in getting my form to authenticate various users? Basically, I'm attempting to have my page to accept a username and password and if the two match, and then route the user to a landing page.

Don't put your authorization token in the body. Put it in the Headers. The first function is going to pass in username, password, and authentication type (ie grant_type=password). Then my second function is going to use that to authenticate the request. There is no longer a need to pass any user information, because my api knows who is requesting based on the token that is passed in. The current documentation for OAuth 2.0 is here, and you can find more information about using headers with fetch at Mozilla's fetch documentation.
// request the token
// subscribe to this event and use the returned json to save your token to state or session storage
export function requestAccessToken(data) {
const loginInfo = `${data}&grant_type=password`;
return fetch(`${API_URL}Token`, {
method: 'POST',
headers: new Headers({
'Content-Type': 'application/x-www-form-urlencoded',
}),
body: loginInfo,
})
.then((response) => response.json());
// in your case set state to returned token
}
// use said token to authenticate request
export function requestUserInfo(token) {
return fetch(`${API_URL}api/participant/userinfo`, {
method: 'GET',
headers: new Headers({
Authorization: `Bearer ${token}`,
}),
})
.then((response) => response.json());
}
I would also recommend:
Calling fetch from a thunk or a saga, but this is out of scope of the question.
No need to put your token in a hidden field. Which is still accessible btw. Just keep it in state. There are other things you can do to secure it a little, but this too, is out of scope of the question.

Related

HTTP Fetch Spotify API token request failure

so basically under guidance of the Spotify WebAPI doc I am trying to request an access token via Client Credentials method. Spotify API Doc. I want to use a regular HTTP fetch request, I can not use any 3rd party libraries. I am getting a 400 return status error response: {error: "unsupported_grant_type", error_description: "grant_type parameter is missing"}. However I believe my request should be formated correctly for its grant type. I have looked at tons of articles, MDN doc, and the Spotify doc and I can not figure out why this is not working. I will include the code which I have obviously taken the api keys out of but they are correct. Link to code.
import React, { Component, useState , useEffect } from 'react';
//Custom IMPORTS:
import '../PageCss/HeaderSection.css'
const Spotify = () => {
const [baseUrl, setBaseUrl] = useState("https://accounts.spotify.com/api/token");
const [token, setToken] = useState([]);
const [currentStatus, setStatus] = useState(false);
const client_id = '';
const client_secret = '';
const data = { grant_type: 'client_credentials' };
useEffect(() => {
fetch(baseUrl,
{
method: 'POST',
headers: {
"Content-Type": "application/x-www-form-urlencoded",
'Authorization': 'Basic ' + (client_id + ':' + client_secret).toString('base64')
},
redirect: 'follow',
body: JSON.stringify(data),
})
.then((response) => {
if (!response.ok) {
return Promise.reject(new Error("Response Error!"));
}
else {
return response.json();
}
})
.catch((err) => {
console.log(err);
})
.then((json) => {
try {
setToken(json.results);
setStatus(true);
console.log("TOKEN:" + token)
}
catch
{
return Promise.reject(new Error(`State Error!: Data: ${token} , Connection:${currentStatus}`));
}
})
.catch((err) => {
console.log(err);
})
}, [baseUrl]);
return (
<div >
</div>
)
};
export default Spotify;
My application is a react app, hosted on GitHub. It's a fully functioning site and everything else is working fine. My other API fetch calls are working fine so I know this one must have an issue in it. The only line of code giving me an error is this 400 status from the fetch request.
Hey so I actually got the inital token request to work with this code:
fetch(baseUrl,
{
method: 'POST',
body: 'grant_type=client_credentials&client_id=' + client_id + '&client_secret=' + client_secret,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
Inspired by the OAuth doc and some more researching. It works now and I have a token from Spotify.

Electron - data from external API being passed to Vue renderer disappears

I'm building an app using Electron, the Quasar UI framework, and Vue 3. The app talks to a Laravel backend using a REST API, but I've had trouble with the login process, particularly passing the data from the main process to the renderer process as it involves promises.
I'm using the #electron/remote to pass the data from the renderer process to the main process. I've set this up in the src/electron-main.js file as per this tutorial.
Here is the code that actually sends the API login request:
src-electron/electron-preload.js
import {contextBridge} from 'electron';
contextBridge.exposeInMainWorld('electronApi', {
getJwtFromServer: async (email, password, server) => {
fetch(
`https://login.${server}/api/v1.1/auth/login`, {
method: 'POST',
mode: 'cors',
cache: 'no-cache',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json',
'x-api-key': '<api key goes here>',
'Cookie': 'XDEBUG_SESSION=PHPSTORM',
},
body: new URLSearchParams({
'email': email,
'password': password
})
}).then(res => {
res.json()
.then(data => {
return data.token;
}).catch(error => {
return error.msg;
})
}).catch(error => {
return error.msg;
})
},
}
});
In the frontend, I then have an Electron API (in TypeScript) which intercepts the data from the main process and forwards it to the renderer:
src/api/electron-api.ts
export interface ElectronApi {
getJwtFromServer: (
email: string,
password: string,
server: string
) => Promise<object>;
}
export const electronApi: ElectronApi = (window as {electronApi: ElectronApi})
.electronApi;
Then in the Vue app I invoke the Electron API, which calls the function in the main process and sends a request to the backend. The correct data is returned to the backend, yet it seems to get lost before it reaches the Vue app.
LoginPage.vue
import {electronApi} from '../api/electron-api';
export default {
data() {
return {
server: '',
email: '',
password: '',
token: ''
}
},
methods: {
loginUser() {
electronApi.getJwtFromServer(this.email, this.password, this.server)
.then((res) => {
console.log("Fetched token: ", res);
this.token = res;
})
},
}
};
The console.log() in the LoginPage.vue component is always undefined. Where is the data getting lost along the way?

Using tokens to authorize in Sanctum

I want to use React with Sanctum. In Sanctum, I generated the token and return it after login, and it works correctly. However, after login, I can see my token in the console, but if I want to open the route for logged-in users, I must send this token in the header. What is the best method to do that?
public function login(Request $request)
{
$attr = $request->validate([
'email' => 'required|string|email|',
'password' => 'required|string|min:6'
]);
if (!Auth::attempt($attr)) {
return $this->error('Credentials not match', 401);
}
return response()->json([
'access_token' => auth()->user()->createToken('auth_token')->plainTextToken,
'token_type' => 'Bearer',
]);
}
And login into React.
function Login () {
const emailRef = useRef(),
passwordRef = useRef();
const handleSubmit = (event) => {
event.preventDefault();
axios.post('http://localhost:8000/api/auth/login', { email: emailRef.current.value, password: passwordRef.current.value })
.then(res => {
console.log(res);
}).catch((error) => {
console.log(error)
});
}
return(
// Here is my form
);
}
#Edit
I don't sure I good understand but it doesn't work. If I try to get address witch need to autorize I have a status 401.
I make instance
instance.php
import axios from 'axios'
let instance = axios.create({
baseURL: 'http://localhost:8000',
headers: {
common: {
'X-Requested-With': 'XMLHttpRequest',
}
},
withCredentials: true,
});
export default instance;
And I login user. In React it look like this
instance.post('/api/auth/login', {
email: emailRef.current.value,
password: passwordRef.current.value
}).then(res => {
console.log(res);
})
Generally it works. I want to add someting which need to authorize.
instance.get('/api/me')
.then(res => {
console.log(res.data.data.token);
}).catch((error) => {
console.log(error)
});
And in Api.php it look like this
Route::post('/auth/register', [AuthController::class, 'register']);
Route::post('/auth/login', [AuthController::class, 'login']);
Route::group(['middleware' => ['auth:sanctum']], function () {
Route::get('/me', function() {
echo "Hello World";
})->name('me');
Route::post('/auth/logout', [AuthController::class, 'logout']);
});
You should be able to not need to store anything in any special place by just using Axios config like this:
axios.create({
headers: {
common: {
'X-Requested-With': 'XMLHttpRequest',
},
},
withCredentials: true,
});
The withCredentials: true will store the CSRF token and any credential once you login, so you would not have to worry anymore about sending any credentials or anything like so.
This is another example.
You could also take advantage of Axios and also delete any data you have store when a request returns 401 (Unauthorized) or 419 (Session Expired).
authClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response &&
[401, 419].includes(error.response.status) &&
store.getters[`user/${IS_GUEST_GETTER}`]
) {
store.dispatch(`user/${LOGOUT_ACTION}`);
}
return Promise.reject(error);
}
);
In this example, I am using a store like vuex and I am asking first if the current user is a guest or not, and then deleting everything from the session with a simple logout (on Vue and Laravel).
You can do this with Interceptors.
How to Fix CSRF token issue using JavaScript (without jQuery)
Error I was facing.
419 PAGE EXPIRED
message : "CSRF token mismatch.", exception: "Symfony\Component\HttpKernel\Exception\HttpException"
My Route is protected with auth:sanctum, above snippet also worked with auth middleware.
Route::post('/media/upload', MediaController::class)
->middleware('auth:sanctum')
->name('media.upload');
1st add <meta name="csrf-token" content="{{ csrf_token() }}"> in app.blade.php
2nd: Get the csrf-token value using document.querySelector('meta[name="csrf-token"]').getAttribute('content')
Snippet headers settings
headers: {
"Accept": "application/json",
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
},
"Accept": "application/json" for 419 PAGE EXPIRED fixing.
'X-CSRF-TOKEN': document.querySelector('me... for CSRF token mismatch.
It worked either you are using axios, Ajax, XHR, uppy.io XHRUpload, or SPA
$.ajax({
type:'POST',
url:'/ajax',
headers: {
"Accept": "application/json",
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
},
});

Login page won't redirect to route to logged-in page

I've been struggling now with my login page to make the component render the Loggedin component.
I'm having Reactjs for the frontend and a NodeJS backend.
I'm fairly new to nodejs, express and react.
On the loginform component I do a post using fetch which passes the username and password to corresponding endpoint on the backend. No problem.
At the backend it reads the jsonfile where i store users(not using any DB for this project) to find a match and if both username and password match the response it send is true. And i've tested that this works.
LoginForm Frontend:
handleSubmit= (e) => {
e.preventDefault()
console.log(this.state)
const { username, password} = this.state;
const data = {username: username, password:password}
fetch('http://localhost:3001/Login', {
method: 'POST',
mode:'cors',
body: JSON.stringify(data),
headers: {
"Content-type": "application/json"
}
})
.then(function(res){
return res.json()}).then(function(resjson){
console.log(resjson);
if (resjson){
console.log(resjson);
return<Redirect to='/myAccount'/>
}
if(resjson==false){
console.log(resjson);
}
})
}
I've been trying to make use of react-router-dom by experimenting. But no matter how i went with it the component for the logged in users never renders evens if resjson is true, not even the route changes to "localhost:3000/myAccount".
I've also tried adding useHistory but that results in an invalid Hook when i add const history=useHistory();
Any ideas? If you need me to add anything else i'll do it, since i'm not that experience when it comes to JS i might have left something important out.
Thanks in advance!
Take a look below, i wrote explanations in comments :
fetch("http://localhost:3001/Login", {
method: "POST",
mode: "cors",
body: JSON.stringify(data),
headers: {
"Content-type": "application/json",
},
})
.then(function (res) {
return res.json();
})
.then(function (resjson) {
console.log(resjson);
if (resjson) {
console.log(resjson);
// here you're returning a React Component and it could work inside render !
// return <Redirect to="/myAccount" />;
// you could use history to redirect:
// if you're inside a class component you could use withRouter HOC instead (there is a link below)
return history.push("/myAccount");
}
if (resjson == false) {
console.log(resjson);
}
});
here is the link for the withRouter HOC use.

React Hooks setUser & setUserId not updating state despite correct data output

I have a functional component using React hooks. There are quite a few functions that will be running in this component, but one in particular is outputting information correctly but not updating the state as it should.
function StatusDropdown() {
const [user, setUser] = useState('')
const [userId, setUserId] = useState('')
useEffect(() => {
getUser()
}, [])
function getUser() {
return fetch(url, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
}).then(response => response.json())
.then(jsonUser => {
setUser(jsonUser.display_name)
setUserId(jsonUser.id)
console.log(`userInfo: ${jsonUser.id} & ${jsonUser.display_name}`)
//This console log returns proper information: userInfo: 7 & Firstname
})
}
}
I stripped away the unnecessary code, but whenever the user and userId state get used further in my component, the user returns undefined. I use the user state to pass it as an API call query, but because it ends up returning undefined, the API does not properly filter data about a specific user.
I'm just trying to understand if I'm missing something specifically. Any pointers are greatly appreciated!
EDIT:
Below is the subsequent code that relies on the user information above:
function getAuxLog(userId) {
return fetch(`${url}?user=${userId}`, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
}).then(response => response.json())
.then(auxlogJson => {
setUserAuxLog(auxlogJson)
setLatestAuxLog(auxlogJson.data[auxlogJson.data.length - 1])
setAuxLogLoaded(true)
})
}
The userId is passed to the API url in order to retrieve a log of activity statuses for a specific user. However, with the userId state not being updated, it can not properly retrieve this logged data.

Categories

Resources