MERN - store user in backend after successful google login - javascript

I am pretty new to Programming and have some questions regarding the MERN stack.
I am building an app and trying to realize the log in via google. I was successful with integrating the google auth to my frontend and now I want to store the user after a successful login in the backend.
The first question I have is do I need a database to store the user, or is it common to just store them on the express backend?
In the auth process I do get the JWT from google and try sending it to the backend, but it does not work.
I do get the following error: "SyntaxError: Unexpected token o in JSON at position 1".
How can I send the JWT to the backend and check, if the user already exists in MongoDB and if he does not exist, make a new entry for the user. And when he is logged in, make a session so he does not have to log in again after every refresh.
At the moment I got the following code for the frontend:
import './App.css';
import { useEffect, useState } from 'react'
import jwt_decode from 'jwt-decode';
import Survey from './components/survey';
function App() {
const [ user, setUser] = useState({});
const [backendData, setBackendData] = useState([{}]);
// fetch backend API, we can define relative route, as proxy is defined in package.json
useEffect(() => {
fetch("http://localhost:5000/api").then(
response => response.json()
).then(
data => {
setBackendData(data)
}
)
}, [])
// store the JWT and decode it
function handleCallBackResponse(response){
console.log("Encoded JWT ID token: " + response.credential);
var userObject = jwt_decode(response.credential);
console.log(userObject);
setUser(userObject);
document.getElementById("signInDiv").hidden = true;
fetch("http://localhost:5000/user", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: userObject,
})
}
// logout the user and show sign in button, google.accounts.id.disableAutoSelect is recording the status in cookies. This prevents a UX dead loop.
function handleSignOut(event){
setUser({});
document.getElementById("signInDiv").hidden = false;
google.accounts.id.disableAutoSelect();
}
useEffect(() => {
/* global google */
// The google.accounts.id.initialize method initializes the Sign In With Google client based on the configuration object.
google.accounts.id.initialize({
client_id: "CLIENT_ID",
callback: handleCallBackResponse
});
// The google.accounts.id.renderButton method renders a Sign In With Google button in your web pages
google.accounts.id.renderButton(
document.getElementById("signInDiv"),
// if only want to display icon
//{theme: "outline", size: "medium", type: "icon"}
{theme: "outline", size: "medium", text: "signin", shape: "square"}
);
// The google.accounts.id.prompt method displays the One Tap prompt or the browser native credential manager after the initialize() method is invoked.
google.accounts.id.prompt();
}, [])
// If we have no user: show sign in button
// if we have a user: show the log out button
return (
<div className="App">
<div id = "signInDiv"/>
{ Object.keys(user).length !== 0 &&
<button className ='signout' onClick={ (e) => handleSignOut(e)}>
{ user &&
<img className='logout' alt="googleprofile" src={user.picture} width='30px' height='30px'></img>
}
</button>
}
<Survey></Survey>
{(typeof backendData.users === 'undefined') ? (
<p>Loading</p>
) : (
backendData.users.map((user, i) => (
<p key={i}>{user}</p>
))
)}
</div>
);
}
export default App;
Backend:
const express = require('express')
const morgan = require('morgan')
const cors = require("cors")
const mongoose = require("mongoose")
const { OAuth2Client } = require("google-auth-library");
const jwt = require("jsonwebtoken");
const app = express()
const uri = "MONGODBURL"
// connect to mongoDB
async function connect() {
try {
await mongoose.connect(uri)
console.log("Connected to MongoDB")
} catch (error) {
console.error(error)
}
}
connect()
// setup view engine, file in "views" folder needs to have ending .ejs
app.set('view engine', 'ejs')
// log requests in the terminal for troubleshooting
app.use(morgan('combined'))
// allow cors URLs
app.use(cors({
origin: ['http://localhost:3000', 'https://play.google.com', 'https://accounts.google.com'],
methods: ["GET", "POST", "PUT", "DELETE"],
credentials: true
}))
app.use(express.json());
// start app on port 5000 and give an log message
app.listen(5000, () => {console.log("Server started on port 5000") })
Hope this make clear what I want to achieve. I really appreciate any help.
Kind Regards

Related

SyntaxError: Unexpected token in JSON at position 0 Express

I am attempting to follow this tutorial (https://levelup.gitconnected.com/introduction-to-express-js-a-node-js-framework-fa3dcbba3a98) to connect Express with React Native via . I have a server.js script running which connects to the client (App.tsx) on my ip, port 3000. The server and app are run simultaneously on the same device in different terminals. The server is able to recieve GET requests just fine, as when the app launches, a useEffect function calls a GET request, and the server sends a message. However my POST requests, which contain a content body set to JSON.stringify("hello world") are not working. and the server script returns the following error anytime I press the button:
SyntaxError: Unexpected token h in JSON at position 0
at JSON.parse (<anonymous>)
...
I'm assuming I am sending in badly formatted json, or haven't set the content type properly, but I haven't been able to figure out the exact problem.
App.tsx (where myip is my ip address):
import { StatusBar } from 'expo-status-bar';
import React, { useEffect, useState } from 'react';
import { StyleSheet, Text, View, ScrollView, TouchableOpacity, TextInput } from 'react-native';
export default function App() {
const [response, setResponse] = useState();
useEffect(() => {
fetch("http://myip:3000/get")
.then(res => res.json())
.then(res => console.log(res.theServer))
}, []);
async function handleSubmit() {
console.log('button press');
const response = await fetch("http://myip:3000/wow/post", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify("hello world")
});
const body = await response.text();
setResponse({ responseToPost: body });
}
return (
<View style={styles.container}>
<TouchableOpacity onPress={handleSubmit}>
<Text>Submit</Text>
</TouchableOpacity>
</View>
}
...
});
server.js
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
const port = process.env.PORT || 3000;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.get("/get", (req, res) => {
res.send({ theServer: "hElLo FrOm YoUr ExPrEsS sErVeR" });
});
app.post("/wow/post", (req, res) => {
console.log(req.body);
res.send(`Here is what you sent me: ${req.body.post}`);
});
app.listen(port, () => console.log(`listening on port ${port}`));
First, stop using body-parser. Express has its own request body parsing middleware.
app.use(express.json())
app.use(express.urlencoded()) // extended = true is the default
The JSON parsing middleware is configured to handle objects by default. While a string literal like "hello world" is valid JSON, it's not what the framework expects, hence your error.
Since you appear to be trying to access req.body.post, you should send your data with such a structure
fetch("http://myip:3000/wow/post", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ post: "hello world" })
})
Alternatively, if you did want to post a JSON string literal, you would need to configure your JSON middleware like so
app.use(express.json({ strict: false }))
strict
Enables or disables only accepting arrays and objects; when disabled will accept anything JSON.parse accepts.
in which case your "hello world" string would appear in req.body
for me, adding
res.header('Access-Control-Allow-Origin', '*');
solved the problem

React 404 Axios Cannot POST

New to Axios and Node as a backend- have inherited some code in React for a login page and am trying to figure out why I can't make a POST to the backend.
This is my .env file:
REACT_APP_BACKEND_URL=http://localhost:3000/admin
And there is a file called API.js
import axios from "axios";
// Set config defaults when creating the instance
export const CompanyAPI = () => {
let api = axios.create({
baseURL: process.env.REACT_APP_BACKEND_URL,
timeout: 10000,
headers: {
"Content-Type": "application/json",
},
});
return api;
};
And then there is a LoginPage.js:
export const LoginPage = () => {
const API = CompanyAPI();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const getAuth = (e) => {
e.preventDefault();
API.post("/auth/login", {
email: email,
password: password,
})
.then(async (response) => {
if (response.status === 200) {
await localStorage.setItem("jwt_key_admin", response.data.token);
setTokenKey(response.data.token);
setIsAuth(true);
navigate("/");
}
})
.catch((error) =>
alert(
"Login Failed"
)
);
};
My question is, is there an example on how I could use express to handle the /auth/login endpoint and complement the existing API.js file?
Basically what I see from this code is:
An axios instance was created and the baseURL was set to being http://localhost:3000/admin
From first glance I can tell that all Api calls that you make a resulting to 404 reason being React locally will always run on port 3000 unless that port is in use.
So now your axios baseURL being to set to port 3000 definitely axios should return a 404 because you surely do not have the endpoint that you are trying to hit
Solution:
Here you are to change the baseURL's port number to the port where Nodejs server is listening on
Then once that is said and done then make sure that even the endpoints that you are trying to hit do exist then your axios calls should work now
Advise:
If the Axios instance created is confusing drop it for a bit and import raw axios not the instance then use axios in it's basic form then once you have that working you surely will have established the correct port number and everything then you can edit the axios instance created with baseURL you have established.
Add
"proxy":"http://localhost:3000/admin"
in your package.json file and restart your React App.

Facing CORS error even after adding CORS options on React/Node/Passport for Google Authentication

I am building a simple app with React as frontend and Node/Express/MongoDB as backend. I am authenticating user using Passport. Local authentication is working, as well as Google authentication.
I just seem to not able to load the google login page through the app. I am getting CORS error. I have shared the error below.
On React Login page:
const onClick = async () => {
await Axios.get('/auth/google');
};
Proxy Middleware:
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
app.use(createProxyMiddleware('/auth', { target: 'http://localhost:4000' }));
};
Node Server.js:
app.use('/auth', require('./routes/auth'));
routes/auth file:
const cors = require('cors');
var corsOptions = {
origin: 'http://localhost:3000',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
preflightContinue: false,
optionsSuccessStatus: 204,
};
router.get(
'/google',
cors(corsOptions),
passport.authenticate('google', {
scope: ['profile', 'email'],
}),
);
router.get('/google/redirect',cors(corsOptions), passport.authenticate('google'), (req, res) => {
res.send(req.user);
});
passportconfig.js:
passport.use(
new GoogleStrategy(
{
clientID: ClientID,
clientSecret: ClientSecret,
callbackURL: '/auth/google/redirect',
proxy: true,
},
(accessToken, refreshToken, profile, done) => {
// passport callback function
//check if user already exists in our db with the given profile ID
User.findOne({ googleId: profile.id }).then((currentUser) => {
if (currentUser) {
//if we already have a record with the given profile ID
done(null, currentUser);
} else {
//if not, create a new user
new User({
googleId: profile.id,
})
.save()
.then((newUser) => {
done(null, newUser);
});
}
});
},
),
);
Error:
Access to XMLHttpRequest at 'https://accounts.google.com/o/oauth2/v2/auth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth%2Fgoogle%2Fredirect&scope=profile%20email&client_id=<clientID>.apps.googleusercontent.com' (redirected from 'http://localhost:3000/auth/google') from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
If I click on the above XMLHttpRequest link, I am able to authenticate and an account is created on my DB with googleID.
I have tried different options suggested throughout internet, but none of them is working for me. I am not sure what is going wrong here.
According to the documentation, try removing the corsOptions entirely and just use the cors() function in your express middle-ware before any router is declared. Like so:
app.use(cors());
Let me know if this works.
// step 1:
// onClick handler function of the button should use window.open instead
// of axios or fetch
const loginHandler = () => window.open("http://[server:port]/auth/google", "_self")
//step 2:
// on the server's redirect route add this successRedirect object with correct url.
// Remember! it's your clients root url!!!
router.get(
'/google/redirect',
passport.authenticate('google',{
successRedirect: "[your CLIENT root url/ example: http://localhost:3000]"
})
)
// step 3:
// create a new server route that will send back the user info when called after the authentication
// is completed. you can use a custom authenticate middleware to make sure that user has indeed
// been authenticated
router.get('/getUser',authenticated, (req, res)=> res.send(req.user))
// here is an example of a custom authenticate express middleware
const authenticated = (req,res,next)=>{
const customError = new Error('you are not logged in');
customError.statusCode = 401;
(!req.user) ? next(customError) : next()
}
// step 4:
// on your client's app.js component make the axios or fetch call to get the user from the
// route that you have just created. This bit could be done many different ways... your call.
const [user, setUser] = useState()
useEffect(() => {
axios.get('http://[server:port]/getUser',{withCredentials : true})
.then(response => response.data && setUser(response.data) )
},[])
Explanation....
step 1 will load your servers auth url on your browser and make the auth request.
step 2 then reload the client url on the browser when the authentication is
complete.
step 3 makes an api endpoint available to collect user info to update the react state
step 4 makes a call to the endpoint, fetches data and updates the users state.

IBM Watson WebSocket Connection failure. HTTP authentication failed; no valid credentials avaliable

I am working on a speech-to-text web app using the IBM Watson Speech to text API. The API is fetched on the click of a button. But whenever I click the button. I get the above-mentioned error. I Have stored my API key and URL in a .env file.
I tried a lot but keep on getting this error. Please Help me out as I am new to all this.
I got server.js from the Watson Github Repo
Server.js
'use strict';
/* eslint-env node, es6 */
const env = require('dotenv');
env.config();
const express = require('express');
const app = express();
const AuthorizationV1 = require('watson-developer-cloud/authorization/v1');
const SpeechToTextV1 = require('watson-developer-cloud/speech-to-text/v1');
const TextToSpeechV1 = require('watson-developer-cloud/text-to-speech/v1');
const vcapServices = require('vcap_services');
const cors = require('cors');
// allows environment properties to be set in a file named .env
// on bluemix, enable rate-limiting and force https
if (process.env.VCAP_SERVICES) {
// enable rate-limiting
const RateLimit = require('express-rate-limit');
app.enable('trust proxy'); // required to work properly behind Bluemix's reverse proxy
const limiter = new RateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
delayMs: 0 // disable delaying - full speed until the max limit is reached
});
// apply to /api/*
app.use('/api/', limiter);
// force https - microphone access requires https in Chrome and possibly other browsers
// (*.mybluemix.net domains all have built-in https support)
const secure = require('express-secure-only');
app.use(secure());
}
app.use(express.static(__dirname + '/static'));
app.use(cors())
// token endpoints
// **Warning**: these endpoints should probably be guarded with additional authentication & authorization for production use
// speech to text token endpoint
var sttAuthService = new AuthorizationV1(
Object.assign(
{
iam_apikey: process.env.SPEECH_TO_TEXT_IAM_APIKEY, // if using an RC service
url: process.env.SPEECH_TO_TEXT_URL ? process.env.SPEECH_TO_TEXT_URL : SpeechToTextV1.URL
},
vcapServices.getCredentials('speech_to_text') // pulls credentials from environment in bluemix, otherwise returns {}
)
);
app.use('/api/speech-to-text/token', function(req, res) {
sttAuthService.getToken(function(err, token) {
if (err) {
console.log('Error retrieving token: ', err);
res.status(500).send('Error retrieving token');
return;
}
res.send(token);
});
});
const port = process.env.PORT || process.env.VCAP_APP_PORT || 3002;
app.listen(port, function() {
console.log('Example IBM Watson Speech JS SDK client app & token server live at http://localhost:%s/', port);
});
// Chrome requires https to access the user's microphone unless it's a localhost url so
// this sets up a basic server on port 3001 using an included self-signed certificate
// note: this is not suitable for production use
// however bluemix automatically adds https support at https://<myapp>.mybluemix.net
if (!process.env.VCAP_SERVICES) {
const fs = require('fs');
const https = require('https');
const HTTPS_PORT = 3001;
const options = {
key: fs.readFileSync(__dirname + '/keys/localhost.pem'),
cert: fs.readFileSync(__dirname + '/keys/localhost.cert')
};
https.createServer(options, app).listen(HTTPS_PORT, function() {
console.log('Secure server live at https://localhost:%s/', HTTPS_PORT);
});
}
App.js
import React, {Component} from 'react';
import 'tachyons';
//import WatsonSpeech from 'ibm-watson';
var recognizeMic = require('watson-speech/speech-to-text/recognize-microphone');
class App extends Component {
onListenClick = () => {
fetch('http://localhost:3002/api/speech-to-text/token')
.then(function(response) {
return response.text();
}).then(function (token) {
var stream = recognizeMic({
token: token, // use `access_token` as the parameter name if using an RC service
objectMode: true, // send objects instead of text
extractResults: true, // convert {results: [{alternatives:[...]}], result_index: 0} to {alternatives: [...], index: 0}
format: false // optional - performs basic formatting on the results such as capitals an periods
});
stream.on('data', function(data) {
console.log('error 1')
console.log(data);
});
stream.on('error', function(err) {
console.log('error 2')
console.log(err);
});
//document.querySelector('#stop').onclick = stream.stop.bind(stream);
}).catch(function(error) {
console.log('error 3')
console.log(error);
});
}
render() {
return(
<div>
<h2 className="tc"> Hello, and welcome to Watson Speech to text api</h2>
<button onClick={this.onListenClick}>Listen to Microphone</button>
</div>
);
}
}
export default App
Since the only code you show is fetching an authorisation token then I guess that that is what is throwing the authentication failure. I am not sure how old the code you are using is, but the mechanism you are using was used when the STT service credentials are userid / password. The mechanism became unreliable when IAM keys started to be used.
Your sample is still using watson-developer-cloud, but that has been superseded by ibm-watson. As migrating the code to ibm-watson will take a lot of rework, you can continue to use watson-developer-cloud.
If do you stick with watson-developer-cloud and you want to get hold of a token, with an IAM Key then use:
AuthIAMV1 = require('ibm-cloud-sdk-core/iam-token-manager/v1'),
...
tokenService = new AuthIAMV1.IamTokenManagerV1({iamApikey : apikey});
...
tokenService.getToken((err, res) => {
if (err) {
...
} else {
token = res;
...
}
});

ApolloServer 2.0 context and public/private parts of the GraphQL API

I'm not a pro in any way but I've started and ApolloServer/Express backend to host a site where I will have public parts and private parts for members. I am generating at JWT token in the login mutation and get's it delivered to the client.
With context I want to check if the token is set or not and based on this handle what GraphQL queries are allowed. My Express/Apollo server looks like this at the moment.
const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => {
// get the user token from the headers
const token = (await req.headers.authorization) || '';
if (token) {
member = await getMember(token);
}
}
});
The problem is that this locks down the GraphQL API from any queries and I want/need to reach signup/login mutations for example.
Could anyone spread some light on this to help me understand what I need to do to get this to work.
the way i am doing it is that i will construct auth middleware even before graphql server as sometimes is needed to have information about authenticated user also in other middlewares not just GraphQL schema. Will add some codes, that you need to get it done
const auth = (req, res, next) => {
if (typeof req.headers.authorization !== 'string') {
return next();
}
const header = req.headers.authorization;
const token = header.replace('Bearer ', '');
try {
const jwtData = jwt.verify(token, JWT_SECRET);
if (jwtData && jwtData.user) {
req.user = jwtData.user;
} else {
console.log('Token was not authorized');
}
} catch (err) {
console.log('Invalid token');
}
return next();
};
This way i am injecting the user into each request if the right token is set. Then in apollo server 2 you can do it as follows.
const initGraphQLserver = () => {
const graphQLConfig = {
context: ({ req, res }) => ({
user: req.user,
}),
rootValue: {},
schema,
};
const apolloServer = new ApolloServer(graphQLConfig);
return apolloServer;
};
This function will initiate ApolloServer and you will apply this middleware in the right place. We need to have auth middleware before applyin apollo server 2
app.use(auth);
initGraphQLserver().applyMiddleware({ app });
assuming the app is
const app = express();
Now you will have user from user jwtData injected into context for each resolver as "user", or in req.user in other middlewares and you can use it for example like this. This is me query for saying which user is authenticated or not
me: {
type: User,
resolve: async (source, args, ctx) => {
const id = get(ctx, 'user.id');
if (!id) return null;
const oneUser = await getOneUser({}, { id, isActive: true });
return oneUser;
},
},
I hope that everything make sense even with fractionized code. Feel free to ask any more questions. There is definitely more complex auth, but this basic example is usually enough for simple app.
Best David

Categories

Resources