Redirect React page to SAML login using Express backend with Passport - node.js

I currently have a functional application in Express, but I am moving to React, thus express should act as an API now. Currently I am moving the login page but I am having a problem I do not know how to solve. For the login I have the following considerations:
Using Passport to authenticate users
Using SAML (SSO) to authenticate them
To be able to authenticate through SSO, the user is redirected to the SSO page and then redirected back to the express app.
This login works with express because I can redirect through different pages. But I am unable to do this with react because I can't find a way to redirect to the SSO login page (redirecting back is done automatically by the SSO site).
This is my current saml login
router.post('/login',
passport.authenticate('saml', {
successRedirect: '/', // SUCCESS: Go to home page
failureRedirect: 'login', // FAIL: Go to /user/login
})
);
This is the form where a user should login
export class Login extends React.Component{
constructor(props){
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(){
fetch("http://localhost:3000/auth/login",
{method : 'POST'})
.then(response => alert(response));
}
render(){
return (
<div className="flex h-screen">
<div className="container m-auto max-w-md w-full space-y-8">
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
Sign in to your account
</h2>
<form className="space-y-6 p-8" onSubmit={this.handleSubmit} id="loginForm">
<div>
<button type="submit">
<span className="absolute left-0 inset-y-0 flex items-center pl-3">
Sign in
</button>
</div>
</form>
</div>
</div>
);
}
As you can see, when a user presses the button, the submit handler is called, where I can do a post request, but I don't know how to continue with the authentication.
I have tried to add a action="http://localhost:3000/auth/login" method="post" to the form. This works, it successfully redirects to the SSO login page but there there are 2 problems here.
The redirect back does not work because it is a post request (it contains user information)
The redirect should be done to the express server, since it is the one that saves the cookies, passport, authentication data and it must complete the redirect.
I am not sure this would work with a real domain, outside local host.
Any ideas?
Thanks!

In the end, to authenticate I created a public subdomain auth.domain.com and launched a window popup to the authentication page, once the page is closed authentication should have been completed.
const authLoginURL = 'http://auth.domain.com/login';
const loginWindow = window.open(
authLoginURL,
"_blank"
);
const timer = setInterval(() => {
if(loginWindow.closed) {
clearInterval(timer);
this.props.callback();
}
}, 1000);

Related

Check whether or not a user is admin

I'm trying to make it so that when a user has admin he should have access to the router. But unfortunately, it does not work, I'm getting an error that isAdmin is not defined, but I have it in the database.
function isAdminLogged(req, res, next) {
if (req.User.isAdmin==="true") return next();
res.redirect('/admin');
}
//ROUTES
app.get('/admin', isAdminLogged, (req, res) => {
res.render("admin", { title: "Admin Area" });
})
Also, I would love to make it when a user is an admin he can see a div in an index.hbs
<div class="home">
<h1>Home</h1>
Logout
</div>
{{#if user.isAdmin}}
<div>
<h1>Hello Guys</h1>
</div>
{{/if}}
I'm new to express and I'm trying my best to understand, thank you in advance!
I have built authentication, and using mongodb but EJS template instead of Handlebars. I think if you go through it then that will help you a lot.
Here is the link to my repo CAMP.
The information that you have provided may be not sufficient to solve your issue from my side.

How to stop firebase re-auth() on every page reload in a react app?

So i have this great react app using firebase auth and firestore.
Everything working fine except
Whenever i reload the page while a user is already logged in... navbar links change for a second.
Looks like app automatically re-login(re-auth) the user on every page reload. Why so? How to get rid of it? Some look-alike code sample
import React, {useState, useEffect} from 'react';
import {Switch, Route} from 'react-router-dom'
import firebase from 'firebase/App'
export const App = () => {
const [isAuth, setIsAuth] = useState()
const auth = firebase.auth()
useEffect(() => {
auth.onAuthStateChanged(user => {
if(user) {
setIsAuth(true)
} else {
setIsAuth(false)
}
})
}, [isAuth])
return(
<div className="App">
<Navbar />
<Switch>
<Route to="/signIn" component={Login} />
<Route to="/signUp" component={SignUp} />
<Route to="/signOut" component={SignOut} />
</Switch>
</div>
)
};
Finally fixed it.
Reason it was happening bcoz firebase servers were verifying the user on each page reload which took some time and cause flickering in navbar for half a second.
Solution has three easy steps
Once logged in, store the user as boolean on local storage.
firebase.auth().onAuthStateChanged(user=>{
if (user) {
// store the user on local storage
localStorage.setItem('user', true);
} else {
// removes the user from local storage on logOut
localStorage.removeItem('user');
}
})
Check The user from local storage in navbar component
const userLocal = JSON.parse(localStorage.getItem('user'));
userLocal ? <SignedInLinks/> : <SignedOutLinks/>;
Remove user from local storage on logout
#illiterate.farmer You are almost right. Actually you should only save a flag, isSignedIn=true or false in the localStorage because saving the full user object makes your app vulnerable to hacking very easily.
Any javascript function can access the localStorage and thus it will expose you tokens that can be used to impersonate as a genuine user to your backend system.
I was having this problem too and I think Firebase's intended way of doing this is to use the FirebaseAuthConsumer... providerId is null when awaiting auth status.
Compare this sandbox where the "not signed in" content is rendered for a split second before the signed in content, with this one where no render happens until Firebase has told us whether or not the user is signed in. Will need to press the "Sign in" button on first load and then refresh to test behaviour.

Authenticated routes with React and PassportJS

I am using the following stack :
React
React router v4
PassportJS
NodeJS backend with Express and
Express session
I have successfully setup PassportJS based login and registration authentication. All pages in my app are protected routes - they can only be viewed by a logged in user.
So, my question is, for each route, how do I check if the user is currently logged in or not. I understand that express session provides server-side session management, but I'm wondering if there's a way to avoid making an API request to the backend on each page load to verify if the session of the current user exists.
My App.js file:
import React, { Component } from 'react';
import './App.css';
import { BrowserRouter, Route, Switch, Link } from 'react-router-dom';
import AsyncAuthPage from 'components/AsyncAuthPage/index.js'
const NoMatch = () => (
<p>Path not found</p>
);
const HomePage = () => (
<div>WELCOME!</div>
);
class App extends Component {
render() {
return (
<div className="App ">
<BrowserRouter>
<Switch>
<Route path="/login" component = {AsyncAuthPage} />
<Route path="/home" component = {HomePage} />
<Route path="*" component={NoMatch} />
</Switch>
</BrowserRouter>
</div>
);
}
}
export default App;
The AsyncAuthPage component implements PassportJS based authentication. In the above sample, I would like to protect Homepage route with authentication. How can this be done? After a user has successfully logged in, the following needs to be taken care of :
The parent App.js needs to know that login was successful
All pages should try to avoid making an API call to the backend (on
componentDidMount or page load) as much as possible, to verify if current user is logged in.
Should work on page reload too

How to resolve issue with handling csrf with multiple tabs in express/nodejs?

I built CSRF protection in my nodejs/express application with the following config:
var app = express(),
cookieParser = require('cookie-parser'),
session = require('express-session'),
csrf = require('csurf');
app.use(cookieParser());
app.use(session({
, saveUninitialized: true
, resave: true
, store: new MongoStore()
}));
app.use(flash());
And with the following login form:
<form action="/process" method="POST">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<button type="submit">Submit</button>
</form>
The problem arives when user opens two browser tabs and end of the story is getting EBADCSRFTOKEN error at this line:
Let's see the following case:
User opens the form above in two separate tabs.
In first tab he do logout and signin again.
Then switches to second tab, click submit and get EBADCSRFTOKEN error.
I need to point that I destroy my session in logout route:
app.route('/auth/signout')
.get(function (req, res, next) {
return req.session.destroy(function (err) {
if (err) return next(err);
return res.redirect('/');
});
});
Because that fact that I destroy the session I destroy the secret also key that stored there. So this destroing leads to invalid token on second tab and EBADCSRFTOKEN error.
I need to resolve this case somehow. What you do in this case? Show popup to reload the page or reload page automatically?
The csrf token should be set and retrieved from cookie before form submission. Suppose, you open tabA with csrf C1. Once you open tab2, the csrf changes to C2. But, if this is set in the cookies, fetching csrf from cookies in tabA will give C2 as csrf token.
Same thing can be concluded with session->logout->new_session. Save and fetch everything from the cookie. Since you logged in after a logout in tab2, tab1 will have cookies of tab2 and also the csrf token.

Get ng-model values on POST to Express.js endpoint

I am creating a Node.js application with AngularJS.
I want to make a simple POST, using Angular. This POST should post a couple of values to my server, where I can see them using console.log.
In my HTML code, I build it with the ng-model and a button that has a ng-click.
I can tell my Node.js server is being hit, as it outputs the post called in the console.
However, I have been trying to read about how to read the POST values, but I haven't found a solution.
How would I modify my code to read serialKey and gameTitle in my Express.js endpoint?
My HTML code:
<div class="input-group" ng-controller="CreateController">
<p>Serial key:<br/>
<input class="form-control" ng-model="serialKey" />
</p>
<p>Game:<br/>
<input class="form-control" ng-model="gameTitle" />
</p>
<span class="input-group-btn">
<button class="btn btn-default"
ng-click="postNewIsbn(serialKey,gameTitle)">Add</button>
</span>
</div>
Angular controller code:
app.controller('CreateController',function($scope, $http) {
var url = '/api/serials';
$scope.postNewIsbn = function(serial, game) {
$http.post(url, {
serial: serial,
gametitle: game
})
.success(function (data) {
$scope.data.status = 'success';
})
.error(function(error) {
$scope.data.error = error;
});
};
});
Express.js endpoint
app.post('/api/serials',function(req,res){
console.log(req.body);
console.log('post called');
});
It appears to be the problem of setting content-type header. In your angular application you can set defaultHeaders for your post request just after you initialize the module or in your config function with this line
$httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
Do remember to inject the $httpProvider dependency whereever you setting this header
UPDATE
It may be the case that you need to configure your express in order to use the bodyParser with this line:
app.use(express.bodyParser());
req.param(name)
When attempting to retrieve data passed with the request, the req.param() function checks the following in order to find the parameter:
req.params
req.body
req.query
See the docs here.
Also, try explicitly setting the content-type header in the POST request to "application/json".

Resources