Accessing Auth0 logged in user from within an API? - javascript

I have a React app which is using auth0 along side an express API server.
My question is, I can get the user information in the client side from the Auth0 user object, but i'm not sure how to access it within the api when a secure end point is called.
Sending the information to the api with any requests seems much less secure than using the access token somehow in the backend but i'm unsure how to do it or if it's even possible.
API SERVER
const express = require('express')
const app = express()
const port = 8000
const jwt = require('express-jwt')
const cors = require('cors')
var jwtCheck = jwt({
secret: '',
audience: 'http://localhost:8000',
issuer: 'https://dev-ml4yg9zg.us.auth0.com/',
algorithms: ['HS256']
});
app.use(cors())
app.get('/unprotected',(req,res) =>{
res.send("not secured resource")
})
app.get('/authed', jwtCheck,(req,res) =>{
// GET THE DATA FOR THE LOGGED IN USER WHO MADE THE CALL
res.send("secured resource")
})
app.listen(port, () =>{
console.log(`app listening on port ${port}`)
})
REACT APP
import React,{useEffect, useState} from 'react';
import axios from 'axios'
import {useAuth0} from '#auth0/auth0-react'
function App() {
const [accessToken, setAccessToken] = useState(null)
const [userMetaData, setUserMetadata] = useState(null)
const {
loginWithRedirect,
logout,
user,
isAuthenticated,
isLoading,
getAccessTokenSilently
} = useAuth0()
console.log(user)
const getToken = async () => {
try {
const accessToken = await getAccessTokenSilently({
audience: `http://localhost:8000`,
scope: "read:current_user",
});
setAccessToken(accessToken)
} catch (e) {
console.log(e.message);
}
};
const callProtected = () =>{
axios.get('http://localhost:8000/authed',{
headers:{
Authorization:`Bearer ${accessToken}`
}
}).then(res =>{
console.log(res.data)
}).catch(e =>{
console.log(e)
})
}
const callUnprotected = ()=>{
axios.get('http://localhost:8000/unprotected')
.then(res =>{
console.log(res.data)
}).catch(e =>{
console.log(e)
})
}
return (
<div className="App">
<button onClick={() => loginWithRedirect()}>Login</button>
<button onClick={() => logout({returnTo:window.location.origin})}>Log out</button>
<button onClick={() => getToken()}>Get token</button>
<button onClick={() => callUnprotected()}>Call unprotected resource</button>
<button onClick={() => callProtected()}>Call protected resource</button>
<div>
User : {user?.name}
</div>
</div>
);
}
export default App;

So I wasn't aware of the User Management API
To solve the issue of getting the user data to confirm authentication within my API server I decoded the JWT token server-side which contained the user ID, then I used the ID within a call to the management API to get the full user data.
The auth token for the call to the management API can be generated within the API dashboard on the Auth0 website
app.get('/authed', jwtCheck, async (req,res) =>{
let token = req.headers.authorization.substring(7, req.headers.authorization.length)
// GET THE DATA FOR THE LOGGED IN USER WHO MADE THE CALL
var decoded = jwt_decode(token);
console.log(decoded.sub)
axios.get(`https://************.us.auth0.com/api/v2/users/${decoded.sub}`,{
headers:{
Authorization:`Bearer *`,
}
}).then((res) =>{
console.log(res)
}).catch(e =>{
console.log(e)
})
res.send("secured resource")
})

Related

Why does my Heroku-deployed React Application only work on Google Chrome?

I am currently having an issue with my React Application not working on any other browser other than Chrome. The Javascript loads just fine with no errors on Chrome and the application is currently fully deployed on Heroku (link: https://weathrd.herokuapp.com/).
In regards to my application, I have a search query set up in the "overview.js" component that creates a "get" request, with a parameter passed in that gets fed into the weather api I am using. Then, I retrieve the json information from the "/forecast" page and feed that back into "overview.js" to display on the screen.
I do not have any regex notation within any of my code, so I don't think that would be an issue here. I also have fully updated my Heroku deploy code and I do not think there is some sort of confusion on Heroku? Regardless, here is my server code, overview component code, and the error I am receiving on Safari:
server code:
const PORT = process.env.PORT || 8000;
const path = require('path');
const express = require('express');
const cors = require('cors');
const axios = require('axios');
require('dotenv').config();
const app = express();
app.use(cors());
app.use(express.static("public"))
app.get('/', (req, res) => {
res.json('hi');
});
app.get('/forecast', (req, res) => {
const options = {
method: 'GET',
url: `http://api.weatherapi.com/v1/forecast.json?`,
params: {
q: req.query.city,
key : process.env.REACT_APP_API_KEY,
days: '3',
api: 'no',
alerts: 'no',
},
};
axios.request(options).then((response) => {
res.json(response.data);
}).catch((error) => {
console.log(error);
});
});
app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT} `))
Safari Error:
The error also mentions the component from which I am making the API request from "overview.js", so here is that code also:
overview.js
import React, {useState} from 'react';
import './overview.css';
import { RecentSearches } from '../Recent Searches/recentSearches';
import { Hourly } from '../Hourly/hourly';
import { Fiveday } from '../5 Day Forecast/fiveday';
import 'animate.css';
const axios = require('axios');
export function Overview() {
const [forecast, setForecast] = useState(null);
// this callback function receives the searched city entered from recentSearches and applies it to fetchForecast
const getSearch = (searchedCity) => {
fetchForecast(searchedCity);
};
async function fetchForecast(searchedCity) {
const options = {
method: 'GET',
url: 'https://weathrd.herokuapp.com/forecast',
params: {city: searchedCity}
};
axios.request(options).then((response) => {
console.log(response.data);
setForecast(response.data);
}).catch((error) => {
console.log(error);
})
};
return (
<div>
<div className='jumbotron' id='heading-title'>
<h1>Welcome to <strong>Weathered</strong>!</h1>
<h3>A Simple Weather Dashboard </h3>
</div>
<div className='container-fluid' id='homepage-skeleton'>
<div className='d-flex' id='center-page'>
<RecentSearches getSearch={getSearch}/>
<Hourly forecast={forecast}/>
</div>
</div>
<Fiveday forecast={forecast}/>
</div>
)
};
Thanks for any assistance!

Spotify node web api - logs out immediately after logging in

I am trying to do Spotify Authentication using Client(React) and Server, the logging in works for a second then the page refreshes immediately after logging in and logs the user out. Anyone knows where might be the problem?
Here is my code:
server.js:
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const SpotifyWebApi = require('spotify-web-api-node');
const app = express();
app.use(cors()) // to handle the cross-origin requests
app.use(express.json()); // to parse JSON bodies
const port = process.env.PORT || 8000;
const credentials = {
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
redirectUri: process.env.REDIRECT_URI || "http://localhost:3000"
};
app.post('/refresh', (req, res) => {
const refreshToken = req.body.refreshToken;
// console.log("Hii");
let spotifyApi = new spotifyWebApi({
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
redirectUri: process.env.REDIRECT_URI,
refreshToken,
});
spotifyApi
.refreshAccessToken()
.then((data) => {
// console.log(data.body);
res.json({
accessToken: data.body.access_token,
expiresIn: data.body.expires_in,
})
})
.catch((err) => {
console.log(err);
res.sendStatus(400);
});
});
app.post('/login', (req,res) => {
// Get the "code" value posted from the client-side and get the user data from the spotify api
let spotifyApi = new spotifyWebApi(credentials)
const code = req.body.code
spotifyApi.authorizationCodeGrant(code).then((data) => {
// Returning the User's Data in the json formate
res.json({
accessToken : data.body.access_token,
refreshToken : data.body.refresh_token,
expiresIn : data.body.expires_in
})
})
.catch((err) => {
console.log(err);
res.sendStatus(400)
})
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
Client side:
useAuth.js:
import React from 'react';
import { useEffect, useState } from 'react';
import axios from "axios"
export default function useAuth(code) {
const [accessToken, setAccessToken] = useState();
const [refreshToken, setRefreshToken] = useState();
const [expiresIn, setExpiresIn] = useState();
useEffect(() => {
axios
.post("/login", {code})
.then((res) => {
window.history.pushState({}, null, "/");
console.log(res.data);
setAccessToken(res.data.accessToken);
setRefreshToken(res.data.refreshToken);
setExpiresIn(res.data.expiresIn);
})
.catch(() => {
window.location = "/";
});
}, [code]);
useEffect(() => {
if (!refreshToken || !expiresIn) {
return;
}
let interval = setInterval(() => {
axios
.post("/refresh", {refreshToken})
.then((res) => {
setAccessToken(res.data.accessToken);
setExpiresIn(res.data.expiresIn);
})
.catch(() => {
window.location = "/";
});
}, (expiresIn - 60) * 1000);
return () => clearInterval(interval)
}, [refreshToken, expiresIn]);
return accessToken;
}
spotifyConfig.js:
const authEndpoint = "https://accounts.spotify.com/authorize";
const redirectUri = "http://localhost:3000";
const clientId = "ea28d4ba34f34b44b59c640052c6e098";
const scopes = [
"streaming",
"playlist-modify-public",
"ugc-image-upload",
"user-read-email",
"user-read-private",
"user-read-currently-playing",
"user-read-recently-played",
"user-read-playback-state",
"user-modify-playback-state"
];
export const loginUrl = `${authEndpoint}?client_id=${clientId}&response_type=code&redirect_uri=${redirectUri}&scope=${scopes.join(
"%20"
)}&show_dialog=true`;
App.js:
import './App.css';
import Dashboard from './pages/Dashboard';
import Login from './components/Login';
const code = new URLSearchParams(window.location.search).get('code')
function App() {
return (
<div>
{code ? <Dashboard code={code}/> : <Login/>}
</div>
);
}
export default App;
👉🏽 this page appears a second then logs out Dashboard.js:
import React, {useEffect} from 'react';
import useAuth from '../useAuth';
import SpotifyWebApi from "spotify-web-api-node";
const spotifyApi = new SpotifyWebApi({
clientId: "ea28d4ba34f34b44b59c640052c6e098",
});
export default function Dashboard({code}) {
const accessToken = useAuth(code);
useEffect(() => {
if (!accessToken) return;
spotifyApi.setAccessToken(accessToken);
spotifyApi.getMe().then(data => {
console.log(data);
})
}, []);
return (
<div>
This is the home page 🏠
</div>
)
}
Login.js:
import React from 'react';
import { loginUrl } from '../spotifyConfig';
export default function Login() {
return (
<div>
<a href={loginUrl}>
<button>LOGIN WITH SPOTIFY</button>
</a>
<div className="links">
<p>
⚠ When joining or creating a Queue, open Spotify to be able to queue up tracks
</p>
</div>
</div>
)
}
I experienced a similar issue. Here are a few steps that helped me to resolve it and many of the subsequent issues I encountered.
Run your IDE's debugger and set break points for your /login request. Also, check whether your environment variables are getting set as you intend (if running VSCode you can learn how to set this up here). In particular, make sure your credentials
clientId: process.env.CLIENT_ID, clientSecret: process.env.CLIENT_SECRET, redirectUri: process.env.REDIRECT_URI
are correct. If your environment variables are not being loaded you won't be able to create a new SpotifyWebApi instance (check out dotenv file is not loading environment variables). Another easy way to check if this is the problem is to hard code your values temporarily.
Test your server /login endpoint independently before running the client to see whether the endpoint is returning 400 or another error when executing requests to Spotify.
Make sure you keep your devtools console open in your browser so you can identify any failed requests you're making to the Spotify API and consider setting devtools to preserve logs in case the page refreshing is deleting them.
If you are running both your client and server from inside VSCode, try instead running them both in new shells outside of VSCode.
I was having a similar issue, having followed this tutorial, and then this YouTube tutorial.
What I realised was that my App component was being rendered twice, which was causing everything to be called twice, including the login endpoint. I was able to verify this using console.log in the endpoint and seeing if the log appeared twice. As the same Spotify code was being used twice in SpotifyWebApi.authorizationCodeGrant, this was what was causing the error.
I was able to trace the issue to the React.StrictMode being enabled, which must have happened when using the create-react-app command. Verify whether the tags appear in your index.js file. For more information, check this StackOverflow answer: My React Component is rendering twice because of Strict Mode

TypeError: jwt.split is not a function at OAuth2Client.verifySignedJwtWithCertsAsync Node package: google-auth-library

The react-google-login from the client react app sends the response back to the Nodejs server with a post request-
client code -
import axios from 'axios';
import React, { Component } from 'react';
import GoogleLogin from 'react-google-login';
import refreshTokenSetup from '../../utils/refreshToken';
const clientId =
'xxxxxx-xfdgsdjg3gfxxxxxxxxxxx.apps.googleusercontent.com';
function Login() {
const onSuccess = (res) => {
console.log('Login Success: currentUser:', res.profileObj);
alert(
`Logged in successfully welcome ${res.profileObj.name} 😍. \n See console for full profile object.`
);
axios
.post('http://localhost:5000/auth/checkToken', { body: res.tokenId })
.then()
.catch((err) => {
console.log(err);
});
};
const onFailure = (res) => {
console.log('Login failed: res:', res);
alert(
`Failed to login. 😢 Please ping this to repo owner twitter.com/sivanesh_fiz`
);
};
return (
<div>
<GoogleLogin
clientId={clientId}
buttonText='Login'
onSuccess={onSuccess}
onFailure={onFailure}
cookiePolicy={'single_host_origin'}
style={{ marginTop: '100px' }}
isSignedIn={true}
/>
</div>
);
}
export default Login;
the backend route-
const { OAuth2Client } = require('google-auth-library');
const key = require('../config/key');
module.exports = {
checkToken: (req, res, next) => {
console.log('checking begins...', req.body);
const client = new OAuth2Client(key.GOOGLE_CLIENT_ID);
async function verify() {
const ticket = await client.verifyIdToken({
idToken: req.body,
audience: key.GOOGLE_CLIENT_ID, // Specify the CLIENT_ID of the app that accesses the backend
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]
});
const payload = ticket.getPayload();
const userid = payload['sub'];
// If request specified a G Suite domain:
// const domain = payload['hd'];
}
verify().catch(console.error);
},
};
The above code is in reference to official Google Documentation available at- https://developers.google.com/identity/sign-in/web/backend-auth
Now everything works fine, user is signed in in the client side, the tokenId is sent back to the server and can be verified by console logging it, even on https://jwt.io/ but the following error is shown-
TypeError: jwt.split is not a function
at OAuth2Client.verifySignedJwtWithCertsAsync (E:\Projects\EAbackend\node_modules\google-auth-library\build\src\auth\oauth2client.js:528:30)
at OAuth2Client.verifyIdTokenAsync (E:\Projects\EAbackend\node_modules\google-auth-library\build\src\auth\oauth2client.js:394:34)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
at async verify (E:\Projects\EAbackend\middleware\auth.js:9:22)
The main issue is that the example from Google doesn't really tell us what is expected as input to the verifyIdToken({options}) function.
This is what Google stated:
After Google returns an ID token, it's submitted by an HTTP POST method request, with the parameter name credential, to your login endpoint.
Which to me, is a little unclear of what is actually sent to the server to be verified. So here is a working example, copied/pasted from https://developers.google.com/identity/gsi/web/guides/verify-google-id-token, but with proper definitions for token and CLIENT_ID that are not mentioned on the Google site.
Server side Node JS:
exports.googleTokenChecker = (request, response) => {
const CLIENT_ID = request.body.clientId;
const token = request.body.credential;
// copied from Google example
const {OAuth2Client} = require('google-auth-library');
const client = new OAuth2Client(CLIENT_ID);
async function verify() {
const ticket = await client.verifyIdToken({
idToken: token,
audience: CLIENT_ID, // Specify the CLIENT_ID of the app that accesses the backend
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]
});
const payload = ticket.getPayload();
const userid = payload['sub'];
// If request specified a G Suite domain:
// const domain = payload['hd'];
}
verify().catch(console.error);
}
Client side HTML to show what is sent to the backend:
<div id="g_id_onload"
data-client_id="CLIENT_ID.apps.googleusercontent.com"
data-callback="handleCredentialResponse"
data-auto_prompt="false">
</div>
<script>
function handleCredentialResponse(response) {
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://localhost:3000/api/google_token_checker", true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(response));
}
</script>
// contents of response parameter
// {
// clientId: 'CLIENT_ID.apps.googleusercontent.com',
// credential: 'JWT_HEADER.JWT_PAYLOAD.JWT_SIGNATURE',
// select_by: 'btn'
// }
Problem is in the idToken: req.body,
req.body has a body object in which the token was present, simply changing it to req.body.body solved the error.
The problem might be very begginner level but took a lot of my time and no online resourse was available which could point me in any direction.
Check the POST Request you will find the error.

Redirect after login using passport js

I have an aplication where i used react and node js. Also i want to create authentification feature. Register page is working ok, i can get data in node js from fron end. But appears a problem when i try to log in. For log in, in node js, i use passport.js:
app.post('/login', checkNotAuthenticated, passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/login',
failureFlash: true
}
));
Also i have the Login component in reactjs:
const Login = () => {
const [name, setName] = useState('')
const [password, setPassword] = useState('')
const nameChange = (e) => {
const val = e.target.value;
setName(val)
}
const passwordChange = (e) => {
const val = e.target.value;
setPassword(val)
}
const register = (e) => {
e.preventDefault()
console.log('login: ' + name + password)
const data = {
name: name,
password: password
};
const requestOptions = {
method: 'post',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify( {name:name,password: password})
};
fetch('http://localhost:4000/login', requestOptions)
.then((response) => response.json())
.then((messages) => {console.log(messages);});
}
return (
< div>
<h1>Login</h1>
<form onSubmit={register}>
<input value={name} onChange={nameChange} type="text" placeholder='name' name='name'/>
<input value={password} onChange={passwordChange} type="text" placeholder='password' name='password'/>
<input type="submit" value='Login'/>
</form>
</div>
)
;
};
export default Login;
The question is, how to connect front end with node js? How to check when the user is loged with success?
You can use session, something like redis to store user's information in server-side and set a cookie in your client side.
In this way, you should generate a random key and store user's data in redis with that.
'hashed-key' : {//user info in redis}
On every request to server, your client side app send the hash-key with cookie, Nodejs app will check the redis for existence of that key and if everything is OK, it will process the request.
In client side you create a state for your app: member or visitor.
It is a good document for the whole idea "Managing Node.js - Express Sessions with Redis"
It is "Create basic login forms using create react app module in reactjs"
And also read "All You Need to Know About User Session Security"
If you use Express server with ReactJS, you don't have to use redirect option when call passport.authenticate.
Because each environment is different.
So if you redirect to "/" in Express, it will be redirect app.get("/)
you can try like this
app.post('/login', checkNotAuthenticated, (req, res, next) => {
passport.authenticate('local', (err, user) => {
if(err) console.error(err);
req.login(user, loginErr => {
if(loginErr) {
console.error(err);
}
return res.json(user);
});
})(req, res);
});
Then, request from React !
fetch('http://localhost:4000/login', requestOptions)
.then((response) => response.json())
.then((user) => { console.log(user); }); // <- Now you get a user!
.catch(err => console.error(err)); // <- If failed login
if you want to route whether user logged in
try like this
const Router = () => {
const [user, setUser] = useState(null);
// when login request success, setUser(user);
return user ? <Main /> : <Login />;
}

Next.js with Express Backend

I have a next.js application that runs with an express server. Everything was fine until the fetch. When I send from front-end to back-end post request, it works fine. And also when I try to fetch some data from another server it works fine. But whenever I want to fetch data from my server(same server as next app), it does not fetch. When I go to the page through the client(routes) it fetches everything, when I refresh and try (server side) does not work... When I console log the data inside getInitialProps it shows on the terminal(server). The error occurs only when I try to fetch something from my server.
Here is my server :
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = express();
server.use(bodyparser.json());
server.use(cors());
server.use("/api", userRoutes);
server.get("/service/:slug", (req, res) => {
return app.render(req, res, "/service", { slug: req.params.slug });
});
server.get("*", (req, res) => {
return handle(req, res);
});
server.listen(port, err => {
if (err) throw err;
console.log(`> Ready on http://localhost:${port}`);
});
});
And this is service page:
import fetch from "isomorphic-fetch";
import Layout from "../components/layout";
const Service = ({ data }) => {
console.log(data,'ins');
return (
<Layout>
<div style={{ padding: "100px" }} className="componentDiv">
<p>xxx</p>
{/* <p>{json.longDesc}</p>
<img src={json.img} alt="" /> */}
</div>
</Layout>
);
};
Service.getInitialProps = async ({ query }) => {
const response = await fetch(
`http://localhost:3000/api/service/${query.slug}`
);
const jsonv = await response.json();
console.log(jsonv);
return { data: jsonv.data };
};
export default Service;
Update!
I used axios instead isomorphic fetch and problem solved.

Categories