Sending data from backend to React frontend via express - no ejs - node.js

I currently have 2 directories in my project, one for backend using express/axios and one for my React frontend. I have a discord authentication page which correctly authenticates and saves a user to my SQL database and express automatically redirects and sends the user information to an html page.
The problem is when I go to redirect the user after authenticating. I want to redirect them back to my React web application and also send the user information to the frontend. I cannot figure out how to do this without using ejs and other third-party applications.
This is my backend code and I want to use res.send(), res.redirect() etc... to be able to give the route which my react page is currently running (localhost:3000) the required data.
const { code } = req.query;
if (code) {
try {
const { data: credentials } = await exchangeAccessCodeForCredentials({
client_id: ID,
client_secret: SECRET,
grant_type: "authorization_code",
code: code.toString(),
redirect_uri: REDIRECT_URL,
});
const { data: user } = await getDiscordUserDetails(credentials.access_token);
const newUser = await createUser(buildUser(user, credentials));
res.setHeader("Auth", newUser.discordId);
res.redirect("http://localhost:3000");
} catch (err) {
console.log(err);
res.sendStatus(400);
}
}
}
I've also tried to retrieve that data from the headers, but the custom headers I set never show up when I log them...
async function trying() {
var req = new XMLHttpRequest();
req.open("GET", document.location, false);
req.send(null);
var headers = req.getAllResponseHeaders().toLowerCase();
alert(headers);
}
Please let me know if there is a way to easily send data to the frontend using only express and React. Thank you

What you need to do, is send all the information of the user to your react application, and handle of the redirection there.
So you have two scenarios:
The authentication is succesful, so you return the user information from your backend, and your React app should redirect to the other page.
The authentication failed, so your return an error or a 403 status code, and your React app should show the error there.

Related

How to only allow function to be ran on frontend after backend authenticates?

I have a frontend in react which displays two buttons currently one for authenticating and one for display data once authenticated
frontend:
const auth = async () => {
window.open("callbackApiRouteOnBackend")
}
const displayData = async () => {
// this function displays our data on frontend
}
return (
<>
<button onClick={auth}>Authenticate</button>
<button onClick={displayData}>Display data</button>
</>
)
authentication is done on the backend using oauth2 to get access token and refresh token this all works fine the only issue is right now the button can be clicked on frontend to display the data when the user isn't authenticated but it will not work as the fetch does not work as no access token. I get that I can use conditional rendering to hide the button when the user is authenticated but I cannot seem to figure out the way to determine on the frontend whether or not the user is authenticated
this is my backend to do the authentication in node js and express:
export const callback = async (req, res) => {
res.redirect("placeholder for external login page");
// then redirect to our redirect function to get access token
};
export const redirect = async (req, res) => {
const link = "external api link to get access token"
try {
const response = await axios.post(link);
// This is where it gets access token
// after access token is got how can i set a state to know the user is authenticated
// and then redirect them back to frontend with the frontend also knowing if
the user is authenticated or not
} catch (error) {
console.log(error);
}
res.redirect(307, "http://localhost:3000");
};
what is the best way to go about this because after the user is authenticated from backend I redirect to frontend but there is no way to tell on the backend or the frontend if the user is actually authenticated or not.
You can send the response to the front end:
res.status(200).send({
accessToken: response.access_token //Not sure what the response object looks like
});
From the front end set that token to localStorage or sessionStorage and check if the token is valid.
Read more: https://tasoskakour.com/blog/react-use-oauth2

Cannot get current user from session in react

I am facing a terrible problem. I am making an app with Google auth functionality. All is done. When I login with Google I am successfully setting a session in my browser's cookie but the problem is I cannot get current user by making request from react but I can get current user from browser
// If I visit this from browser I can get current user
https://localhost:8000/api/v1/auth/success
//but the same not work when I make request with react
const fetchUser = async () => {
const { data } = await axios.get(
"https://localhost:8000/api/v1/auth/success"
);
console.log(data);
try {
} catch (err) {}
}
Someone please solve my problem if you want I can show you my code via any way.

Is the Google oAuth process right for react native apps

I am trying to put Google signin in place for a react native app in iOS along with the NodeJS backend.
The FE uses this library to configure and get auth code from Google.
https://github.com/react-native-google-signin/google-signin
In the backend I am verifying the auth code by sending a API to google endpoint with the following params:-
Backend code sample:-
const GOOGLE_IOS_CLIENT_ID = '123456789-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com';
const GOOGLE_IOS_CLIENT_SECRET = 'REDACTED';
// const GOOGLE_IOS_REDIRECT_URI = 'com.smartappstechnology.kravein';
const GOOGLE_IOS_REDIRECT_URI = 'com.googleusercontent.apps.123456789-abcdefghijklmnopqrstuvwxyz';
export async function get_google_tokens(logging_key, { code }) {
const GRANT_TYPE = 'authorization_code';
try {
const endpoint = `https://oauth2.googleapis.com/token?client_id=${GOOGLE_IOS_CLIENT_ID}&client_secret=${GOOGLE_IOS_CLIENT_SECRET}&code=${code}&grant_type=${GRANT_TYPE}&redirect_uri=${GOOGLE_IOS_REDIRECT_URI}`;
const response = await post_to_API(logging_key, endpoint, {});
return response.body;
} catch (error) {
throw error;
}
};
I am getting a error like this from post_to_api response from Google:-
{
"error": "invalid_request",
"error_description": "Invalid parameter value for redirect_uri: Missing scheme in the URI"
}
I tried changing the redirect_uri values, but still the same issue.
I doubt whether the process I am doing itself is not correct. I experimented most of the ideas from google docs. Nothing works.
Would be helpful if anyone can refer the links of exact documentation of how this is done for react native apps in iOS with nodejs backend.
Thanks in advance

Maintaining express sessions when moving between static and dynamic pages on client side

I am trying to set up express sessions for user authentication.
I have a node.js backend, and angular and static page front end (two front ends).
My node backend accepts username and password for authentication on my route
as a post request http://localhost:3000/users/login
My journey is as follows:
1. User is presented with a static front end login page, (this is designed by injecting vue.js with axios on a static html page), wherein he requests a log in with his credentials. The client frontend is hosted on http://localhost:3011/
2. The application will now send a post request to http://localhost:3000/users/login to verify the credentials.
3. If the credentials are valid, the server will create a new session (express-session) using req.session. In this session, I also store my token that I use to authenticate the user for any subsequent requests (this token is saved in the db and verified for all further requests). The server will then respond with a 200 OKAY status.
4. Once the vue application gets a positive response, the client will redirect the application to http://localhost:3000/ where I have angular application in the dist folder.
If, in POSTMAN, I do a post to http://localhost:3000/users/login and then do a GET on http://localhost:3000/, I can see the token in the logs when the server responds to the GET request.
But, if i send a post request to http://localhost:3000/users/login from my vue application, and then redirect to http://localhost:3000/ on successful authentication, I cannot see the token in the logs.
Code Snippet on the client side (Vue)
submit() {
this.errMsg = "";
axios
.post("http://localhost:3000/users/login", {
user: this.user,
password: this.password
})
.then(response => {
window.location = "http://localhost:3000/";
})
.catch(err => {
console.log(err);
this.errMsg = err.data.statusText;
});
}
Code inside login on server side
router.route("/login").post((req, res) => {
let { user, password } = req.body;
merchant_table
.findOne({ attributes: ["id"], where: { email, password } })
.then(result => {
if (result) {
let token = 12345;
token_table
.create({ m_id: result.id, token })
.then(result => {
req.session.user = result.token;
console.log("session is", req.session); //THIS SHOWS THE TOKEN
res.status(200).json({status:"OKAY!"})
});
} else {
res.status(401).json({status:"Invalid User!"})
}
})
.catch(error => console.log(error));
});
Code inside the request API for http://localhost:3000/
app.use("/", function(req, res) {
console.log("Session is", req.session); //THIS SHOWS THE TOKEN FOR POSTMAN, NOT WHEN WORKING WITH VUE
res.sendFile(__dirname + "/dist/index.html");
});
Since the axios post request and the subsequent redirect is to http://localhost:3000/ I expected the token to be maintained. But it seem to assume this as a new connection. Is this approach wrong? Is there a way to keep track of the token on redirect? I need the token only once, because then I will store it in the angular application and send it for any other requests that I need.
How do I go about with this?

Authentication on Server side routes in Meteor

What is the best way (most secure and easiest) to authenticate a user for a server side route?
Software/Versions
I'm using the latest Iron Router 1.* and Meteor 1.* and to begin, I'm just using accounts-password.
Reference code
I have a simple server side route that renders a pdf to the screen:
both/routes.js
Router.route('/pdf-server', function() {
var filePath = process.env.PWD + "/server/.files/users/test.pdf";
console.log(filePath);
var fs = Npm.require('fs');
var data = fs.readFileSync(filePath);
this.response.write(data);
this.response.end();
}, {where: 'server'});
As an example, I'd like to do something close to what this SO answer suggested:
On the server:
var Secrets = new Meteor.Collection("secrets");
Meteor.methods({
getSecretKey: function () {
if (!this.userId)
// check if the user has privileges
throw Meteor.Error(403);
return Secrets.insert({_id: Random.id(), user: this.userId});
},
});
And then in client code:
testController.events({
'click button[name=get-pdf]': function () {
Meteor.call("getSecretKey", function (error, response) {
if (error) throw error;
if (response)
Router.go('/pdf-server');
});
}
});
But even if I somehow got this method working, I'd still be vulnerable to users just putting in a URL like '/pdf-server' unless the route itself somehow checked the Secrets collection right?
In the Route, I could get the request, and somehow get the header information?
Router.route('/pdf-server', function() {
var req = this.request;
var res = this.response;
}, {where: 'server'});
And from the client pass a token over the HTTP header, and then in the route check if the token is good from the Collection?
In addition to using url tokens as the other answer you could also use cookies:
Add in some packages that allow you to set cookies and read them server side:
meteor add mrt:cookies thepumpinglemma:cookies
Then you could have something that syncs the cookies up with your login status
Client Side
Tracker.autorun(function() {
//Update the cookie whenever they log in or out
Cookie.set("meteor_user_id", Meteor.userId());
Cookie.set("meteor_token", localStorage.getItem("Meteor.loginToken"));
});
Server Side
On the server side you just need to check this cookie is valid (with iron router)
Router.route('/somepath/:fileid', function() {
//Check the values in the cookies
var cookies = new Cookies( this.request ),
userId = cookies.get("meteor_user_id") || "",
token = cookies.get("meteor_token") || "";
//Check a valid user with this token exists
var user = Meteor.users.findOne({
_id: userId,
'services.resume.loginTokens.hashedToken' : Accounts._hashLoginToken(token)
});
//If they're not logged in tell them
if(!user) return this.response.end("Not allowed");
//Theyre logged in!
this.response.end("You're logged in!");
}, {where:'server'});
I think I have a secure and easy solution for doing this from within IronRouter.route(). The request must be made with a valid user ID and auth token in the header. I call this function from within Router.route(), which then gives me access to this.user, or responds with a 401 if the authentication fails:
// Verify the request is being made by an actively logged in user
// #context: IronRouter.Router.route()
authenticate = ->
// Get the auth info from header
userId = this.request.headers['x-user-id']
loginToken = this.request.headers['x-auth-token']
// Get the user from the database
if userId and loginToken
user = Meteor.users.findOne {'_id': userId, 'services.resume.loginTokens.token': loginToken}
// Return an error if the login token does not match any belonging to the user
if not user
respond.call this, {success: false, message: "You must be logged in to do this."}, 401
// Attach the user to the context so they can be accessed at this.user within route
this.user = user
// Respond to an HTTP request
// #context: IronRouter.Router.route()
respond = (body, statusCode=200, headers) ->
this.response.statusCode statusCode
this.response.setHeader 'Content-Type', 'text/json'
this.response.writeHead statusCode, headers
this.response.write JSON.stringify(body)
this.response.end()
And something like this from the client:
Meteor.startup ->
HTTP.get "http://yoursite.com/pdf-server",
headers:
'X-Auth-Token': Accounts._storedLoginToken()
'X-User-Id': Meteor.userId()
(error, result) -> // This callback triggered once http response received
console.log result
This code was heavily inspired by RestStop and RestStop2. It's part of a meteor package for writing REST APIs in Meteor 0.9.0+ (built on top of Iron Router). You can check out the complete source code here:
https://github.com/krose72205/meteor-restivus
Because server-side routes act as simple REST endpoints, they don't have access to user authentication data (e.g. they can't call Meteor.user()). Therefore you need to devise an alternative authentication scheme. The most straightforward way to accomplish this is with some form of key exchange as discussed here and here.
Example implementation:
server/app.js
// whenever the user logs in, update her apiKey
Accounts.onLogin(function(info) {
// generate a new apiKey
var apiKey = Random.id();
// add the apiKey to the user's document
Meteor.users.update(info.user._id, {$set: {apiKey: apiKey}});
});
// auto-publish the current user's apiKey
Meteor.publish(null, function() {
return Meteor.users.find(this.userId, {fields: {apiKey: 1}});
});
lib/routes.js
// example route using the apiKey
Router.route('/secret/:apiKey', {name: 'secret', where: 'server'})
.get(function() {
// fetch the user with this key
// note you may want to add an index on apiKey so this is fast
var user = Meteor.users.findOne({apiKey: this.params.apiKey});
if (user) {
// we have authenticated the user - do something useful here
this.response.statusCode = 200;
return this.response.end('ok');
} else {
// the key is invalid or not provided so return an error
this.response.statusCode = 403;
return this.response.end('not allowed');
}
});
client/app.html
<template name="myTemplate">
{{#with currentUser}}
secret
{{/with}}
</template>
Notes
Make /secret only accessible via HTTPS.
While it's very likely that the user requesting /secret is currently connected, there is no guarantee that she is. The user could have logged in, copied her key, closed the tab, and initiated the request sometime later.
This is a simple means of user authentication. I would explore more sophisticated mechanisms (see the links above) if the server-route reveals high-value data (SSNs, credit cards, etc.).
See this question for more details on sending static content from the server.
I truly believe using HTTP headers are the best solution to this problem because they're simple and don't require messing about with cookies or developing a new authentication scheme.
I loved #kahmali's answer, so I wrote it to work with WebApp and a simple XMLHttpRequest. This has been tested on Meteor 1.6.
Client
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
// Skipping ahead to the upload logic
const xhr = new XMLHttpRequest();
const form = new FormData();
// Add files
files.forEach((file) => {
form.append(file.name,
// So BusBoy sees as file instead of field, use Blob
new Blob([file.data], { type: 'text/plain' })); // w/e your mime type is
});
// XHR progress, load, error, and readystatechange event listeners here
// Open Connection
xhr.open('POST', '/path/to/upload', true);
// Meteor authentication details (must happen *after* xhr.open)
xhr.setRequestHeader('X-Auth-Token', Accounts._storedLoginToken());
xhr.setRequestHeader('X-User-Id', Meteor.userId());
// Send
xhr.send(form);
Server
import { Meteor } from 'meteor/meteor';
import { WebApp } from 'meteor/webapp';
import { Roles } from 'meteor/alanning:roles'; // optional
const BusBoy = require('connect-busboy');
const crypto = require('crypto'); // built-in Node library
WebApp.connectHandlers
.use(BusBoy())
.use('/path/to/upload', (req, res) => {
const user = req.headers['x-user-id'];
// We have to get a base64 digest of the sha256 hashed login token
// I'm not sure when Meteor changed to hashed tokens, but this is
// one of the major differences from #kahmali's answer
const hash = crypto.createHash('sha256');
hash.update(req.headers['x-auth-token']);
// Authentication (is user logged-in)
if (!Meteor.users.findOne({
_id: user,
'services.resume.loginTokens.hashedToken': hash.digest('base64'),
})) {
// User not logged in; 401 Unauthorized
res.writeHead(401);
res.end();
return;
}
// Authorization
if (!Roles.userIsInRole(user, 'whatever')) {
// User is not authorized; 403 Forbidden
res.writeHead(403);
res.end();
return;
}
if (req.busboy) {
// Handle file upload
res.writeHead(201); // eventually
res.end();
} else {
// Something went wrong
res.writeHead(500); // server error
res.end();
}
});
I hope this helps someone!
Since Meteor doesn't use session cookies, client must explicitly include some sort of user identification when making a HTTP request to a server route.
The easiest way to do it is to pass userId in the query string of the URL. Obviously, you also need to add a security token that will prove that the user is really who the claim they are. Obtaining this token can be done via a Meteor method.
Meteor by itself doesn't provide such mechanism, so you need some custom implementation. I wrote a Meteor package called mhagmajer:server-route which was thoroughly tested. You can learn more about it here: https://blog.hagmajer.com/server-side-routing-with-authentication-in-meteor-6625ed832a94

Resources