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.
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
I'm stuck on this very simple problem and I don't know what I am doing wrong. I'm creating a simple RESTAPI with users and authentication. This is the GET request that I'm trying to run:
router.get('/users/me',auth ,async(req,res)=>{
console.log(req.user)
console.log('Entered the get request')
res.send(getUserData(req.user)) })
Basically, at the end of the authentication, I save the user fetched from the database into req.user object and then I send it to the client using res.send(req.user). As you can see in the code below:
const auth = async(req,res,next)=>{
try{
const token = req.header('Authorization').replace('Bearer ','')
const decoded = jwt.verify(token,'secretkey')
const db = req.app.locals.database
const user = await db.collection('users').findOne({
_id: decoded.id,
'tokens.token':token
})
console.log(user)
if(!user){
throw new Error()
}
req.user = user
next()
}catch(e){
res.status(401).send('Please Authenticate')
}}
The problem is that this authentication function is working perfectly fine. I have used it on other routes as well. But in this GET request, the authentication function runs (I identified this using many console logs) and fetches the data correctly. But it doesn't enter the get request once the middleware(auth function) has completed the call.
From POSTMAN, when I send GET requests, I get a 404 error and I have no idea why. I have literally spent hours trying to make sense of this. Please help me out if you know what might be the problem.
EDIT: So I was able to run the get request by moving the request above the following request that I also have in the same file:
router.get('/users/:id',auth ,async(req,res)=>{
const id = req.params.id
const db = req.app.locals.database
try{
const result = await db.collection('users').findOne({_id: id})
const userData = getUserData(result)
res.status(200).send(userData)
}catch(e){
res.status(404).send()
}
})
But now this request doesn't work. I cant figure out what positioning has to do with requests functionality. Can anyone please explain?
I am using react node js with SQL.
When I logout from reactJS I am removing the token while logging out. But still when i login within different credentials it takes me to dashboard with old user data and when I refresh page i get the data with new currently logged in user.
What could be it
The code part is pretty much correct I guess because it shows perfect data after page refresh.
my logout action
export const logoutAction = () => {
return dispatch => {
localStorage.removeItem('tkn')
dispatch({ type: LOGOUT, payload: "" })
}
}
logout reducer
case LOGOUT:
return {
...state,
token: action.payload,
isAuth: false,
}
Triggered action from the navbar
const logout = () => {
props.logoutUser()
handleDrawerClose()
}
I Had the same problem. My APP get a JWT auth token from a ASP.NET Web API and use this for display some items after login. But when the user loggout and other user does login, the data from the first user apear instead off the last user data.
I found a session cookie and I resolved the problem installing the react-native-cookies package (#react-native-cookies/cookies). https://github.com/react-native-cookies/cookies
yarn add #react-native-cookies/cookies
After this, on my AuthActions, on my Logout routine, I call clearAll() method.
import CookieManager from '#react-native-cookies/cookies';
export const userLogout = () => {
CookieManager.clearAll();
return (dispatch) => {
dispatch({type: LOGOUT_ACTION});
// more actions...
};
};
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