how to use passport SAML with both node (express) as backend and vue as client - node.js

I have an app with node (express) backend, and vue client.
I'm trying to add SAML SSO using passport. (makes sense to do it on the server node app).
it works perfect when used in express app. but when I applied it to a structure of express backend and vue client - it fails to make the redirection to the Idp.
when user enters my login page, vue client (Login.vue) calls node backend for verifying the user. (api verifyuser)
node call passport.authenticate('saml', ...) and I expected a response I can send back to the vue function that called me, and there, in Login.vue - to make the redirection.
but here comes the problem:
in the backend node app, the redirect response is sent after my code is executed, inside passport strategy. So it is sent automatically to the browser, not returning to the vue script that called this node api.
So the redirection is done in the background, the user don't see any redirect. the original login page is still shown.
And my vue function gets the response back from the API - only after the browser sends the redirect (in the background) to the IDP, and gets the login html page response from the IDP.
So the data I get back - is an html of the IDP login page, instead of a redirection data.
How can I solve it?
I'm new to client technologies and js and node including, so I really don't know how such a flow should be handled. searching 3 days for solution.
Thanks a lot for you assistance!
here is my snippets of code:
Login.vue:
<input class="button wide cropBottom io-mango ae-5 margin-top-0 toRight" v-on:click="userLogin" type="button" value="Log In"/>
...
userLogin: function() {
...
...
$(".overlay").show();
this.$http.post(process.env.BASE_URL + "verifyuser", oUser) //call backend node express app
.then(function(data) {
...
here I gets only an html login page which the IDP sent as a response to the redirect with the SAML Request.
}
Backend node express app:
verifyuser.js:
module.exports = function (app) {
app.post('/verifyuser', (req, res, next) => {
var SamlStrategy = passportSaml.Strategy;
passport.use(new SamlStrategy(
{ ...
});
passport.authenticate('saml', {session: false}, function (err, user, info) {
...
})(req,res,next);
//tried to get the redirect here, but res still don't have it. only after this function is called, the SAML request is created from the req, and the location url is made.
});

I've found a solution.
I changed the Vue client:
instead of calling the server using ajax, and expecting a data response to come back,
I called the server using post of a form.
that way, the browser redirects to the server when I call it, and when the passport library in the server returns a redirect response- it is done in the forground, and the user can see it.
In Single logout, passport have done a better job:
the passport API just returns the logout request created.
then I can decide myself if I want redirect from the server, or I want to send the redirection request to the waiting client function - and do the redirection from there.

Related

firebase onAuthStateChanged infinite loop node js

I'm using firebase to sign in my users on my node js app. I would like to see if the user is authentificated or not and after it redirect to the page I want (login if it not logged or dashboard).
But when I redirect user (if it not logged previously or session expires) it's looping on the same page (send redirect of the login page everytime when I'm on login page).
My function that I use actually :
function authenficated (req, res, next) {
firebase.auth().onAuthStateChanged(function (user) {
if (user) {
console.log("connected" + " " + user.uid);
next()
} else {
console.log("disconnected")
res.redirect('/') //loop on / page
next()
}
});
}
I would like a function that provides if my user is logged or not, if it logged my node backend return to /dashboard or other pages that I want and if not it cannot access to dashboard and it return automatically to / or /login
I specify I don't use React or Vue, I use simply EJS to display my pages
Thanks for all
This function/sdk is meant for frontend applications and not backend apps. You need to the admin sdk for that. You can use cookies and the admin sdk provides a function to create cookies. After a signin you attach the cookie to the headers and it will be send by the browser on every request. If the cookie header is empty than you know the user isn't signed in. To logout a user you can add a head method to clear the cookie.
To use backend function you need to use the admin sdk. This function is a front end function (web sdk ).
You can use onAuthStateChanged on the front end and redirect them from the front end. Remember onAuthStateChanged will fire on every page load.
OR implement cookies like the previous comments.
OR
Send the id token from the client via http request (fetch or axios) and verify server side using the admin sdk. Here is the specific link. This solution would require you to load something on the front end though and then send a http request to the backend, verify, then send protected resources after.
Cookies on the other hand are sent to the backend with every request, so if no cookie is present on the page load request then obviously there is no user. Or if the below function fails then server wont send protected resources. (this is explained in the link above for cookies)
getAuth().verifySessionCookie(sessionCookie, true /** checkRevoked */)

React + Express With Passport.js

I am setting up a full-stack application using React and Express JS.
I'm using Passport.js for authentication and have come across a slight problem...
So my front-end and back-end are two separate packages running on two different ports. On my express app, I have created a route like the following.
app.post('/api/account/login', (req, res, next) => {
passport.authenticate('local', {
successRedirect: '/dashboard',
failureRedirect: '/users/login',
}) (req, res, next);
});
This is pretty standard as far as Passport.js goes. Basically, if it authenticates the credentials I have provided, then it should redirect me to /dashboard. If not, then to the other mentioned route.
I am able to call reach this endpoint from my react application and get the correct response like the following in the network tab of chrome.
Request URL: http://localhost:3000/dashboard
Request Method: GET
Status Code: 304 Not Modified
Remote Address: 127.0.0.1:3000
Referrer Policy: no-referrer-when-downgrade
However, it doesn't actually redirect me to /dashboard. Is it not possible to do it this way?
Thanks.
It sounds like your React app is calling the route via ajax using something like fetch.
The way you're using Passport assumes that a browser is issuing the requests directly. On a successful login, Passport returns a Redirect response (HTTP 302 or similar), which the browser honors and redirects the user to.
Ajax requests don't work this way, since there isn't any navigation happening.
You'll need to handle this yourself on the React side of things. Your Express app will need to handle the session authentication by (for instance) returning a JSON message with a token or storing a session cookie. You'll need to update your React app to recognize this and then navigate to the correct route via client-side Javascript.
If you're using react-router, they have some sample code that might be helpful.

How cookies are handled in react-native?

I have a web app running with node js and passport.js and the authentication flow is working well.
I'm trying to develop a react-native and to make the same authentication flow (with passport.js).
I changed the passport code to redirect back to the react-native app (with Linking) and it worked.
so the flow is:
Open browser with the login url (/auth/google)
User logged in
Redirected back to native app
send a request to verify the user is logged in - but the user is not logged in, I think because the cookies were not sent to the server
I also tried adding to the fetch credentials "same-origin" or "include" but still the user is not logged in.
Some code I used:
Linking.openURL("http://<my ip>:3000/auth/google"); //for log in
app.get('/auth/google/callback', //handle the log in with passport js
passport.authenticate('google', {
failureRedirect: '/login'
}), function(req, res) {
res.redirect('MyApp://login); // redirect back to native app
});
fetch("http://<my ip>:3000/api1", {credentials: /*"same-origin"*/"include"}) //get 401 -> user is not logged in
Am I missing anything?
How cookies are handled in react-native? Is it like in the web? how the cookies should be passed from the browser to the native app after redirect?
React Native is not browser environment. It doesn't automatically handle cookies like browsers.
You have to use some cookie-aware request library such as superagent.
Example usage (from superagent documentation):
const agent = request.agent();
agent
.post('/login')
.then(() => {
return agent.get('/cookied-page');
});
Then all requests from agent will handle cookies automatically (shares the same cookie jar).

How can I redirect a page from an external Node/Express server

I have a React/Node/Express web app up and running with two separate servers. React is running on localhost:8080 and proxying API requests to my node server running on localhost:3000. This will be my setup for production, so if possible, I would like to leave this structure intact.
The problem I'm running into is part of my API needs to redirect the user to a page to grab a token from the Spotify API, and then redirect back to my correct page upon successful authentication.
The API call proxy is being done like this:
const axios = require('axios');
const axiosInstance = axios.create({
baseURL: process.env.NODE === 'production' ? '' : 'http://localhost:3000'
});
module.exports = axiosInstance;
Up to this point, this works great, as I can keep my React and Node server completely separate.
As I mentioned above, I need to redirect my React front-end through an API call on my back-end, to a different page (Spotify authentication) to authenticate users, which then Spotify redirects back to my site after a user allows or disallows access to their Spotify accounts.
On my front-end - localhost:8080, the user clicks a button which calls this function.
authenticate = () => {
axios.get('/login')
.then(data => console.log(data));
}
Which calls this endpoint on localhost:3000.
router.get('/', (req, res) => {
let scopes = 'user-read-private user-read-email';
let client = process.env.SPOTIFY_CLIENT;
let redirect = 'http://localhost:3000/login/redirect';
res.redirect(`https://accounts.spotify.com/authorize?response_type=code'
&client_id=${client}${scopes ? `&scope=${encodeURIComponent(scopes)}` : ''}
&redirect_uri=${encodeURIComponent(redirect)}&state=${state}`);
});
When I click the login button on my front-end, I get this response, which I believe to be from Spotify as I can successfully make cross-origin requests to other endpoints in my API.
Failed to load... No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.
However, when I manually navigate to localhost:3000/login, the redirect works and I'm sent to the Spotify authentication page.
In addition, I tested trying to redirect my front-end to any url, and it doesn't work.
So my question is, how can I redirect my front-end through an API call from a server that doesn't also serve my static front-end content?
The reason this isn't working is because your axios.get('/login') is an AJAX request to your server which your server is then telling the AJAX request to be redirected to Spotify, but Spotify doesn't have your front-end server registered so you get the CORS error, since your request was initiated while on the front-end server URL.
Your AJAX request is following that redirect that was given, not trying to load your browser on http://localhost:3000, which would then redirect it to the Spotify authorization page.
Now, rather than doing:
authenticate = () => {
axios.get('/login')
.then(data => console.log(data));
}
You could do:
authenticate = () => {
window.location.href = "http://localhost:3000"
}
This would direct your front-end application to visit the URL of your API server and follow the redirect immediately to Spotify and then you could authorize and be redirected back to your API server, which could then redirect back to your front-end application.
Truthfully, this is a bit overkill, since presumably your React application and node application look like they would be running on the same domain outside of development, given the relative URL's you're using. If you're using create-react-app, you should follow their instructions on configuring your proxy via this or manually via this.
tl;dr you need to tinker with how your proxy is configured or change from doing an AJAX request there to actually going directly to /login (not doing an AJAX request).

How to make Express return a new html with axios post

I have an express server. I have two routes as get methods.
app.get('/main',(req, res) => {
res.sendFile(`main.html`, {root: staticPath});
});
app.get('/signin', (req, res) => {
res.sendFile('signin.html', {root: staticPath});
});
I want to build my app as a single page react application. But before I let the user see this single page, I want to show a sign in, sign up screen. So when user clicks the sign in or sign up buttons, I want to send signin.html as response from the express server.
Here is my code on the browser from a react class
SignIn(){
axios.get('signin');
}
I can console.log() from the express route and verify that the code gets executed within the 'signin' route, but the html view doesn't change on the browser even though I send back a html file. How do I make it happen?
I'm by no means an expert, but here are my two cents. Instead of setting up your front end to receive an HTML file from the server, a more efficient approach would be the following.
Build the signup and login pages on the front end.
Set up routing between these pages.
Send the login/signup details from client to server via /login or /signup routes that you set up in Express. These details would usually be in the req.body object (make sure to install the bodyparser package from NPM).
You could then use JWTs to authenticate users and maintain sessions.
If you're looking for server-side rendering with React, here is an article for your reading pleasure :) Sorry if I made no sense.

Resources