Authenticate static pages with passportjs and express - node.js

I am using passportjs to authenticate a simple web app. I am able to store the session and check whether the user exists using req.user on pages that are making calls to my node server. However, there I also have static pages that are not making calls -- if someone were to guess the URL of these pages, they'd be able to get into the app without authentication. How can I authenticate these static pages through the server?

You could introduce authentication for static files by adding auth middleware to your route.
var jwt = require('express-jwt');
var auth = jwt({secret: 'SECRET', userProperty: 'payload'});
app.use(auth, express.static('public'));
If you dont use jwt, a custom function can be passed instead of auth.

app.use('/pagecontainer', yourAuthFunction, express.static(__dirname + '/public/pagecontainer'));
Define a static route to access the folder where your pages are in, and add the passport authentication (yourAuthFunction) middle-ware function to authenticate requests coming to that folder.

Related

Handle login for API and static page in node js

I'm working on MERN stack application. There are two types of endpoints in my applications. One for API and another for static routes (using handlebars in NodeJS). I have used the basic JWT token to secure the API endpoints which would be accessible by React App. But JWT is not useful for static routes.
So How can I use single authentication for both types of routes?
Static routes are preview URL which is generated by admin panel. Only authenticated users can access them. HubSpot has the same strategy for the pages, Preview URL of pages can only be accessible by authenticated users.
Is there any other way to authenticate both endpoint types?
We can try the following way to achieve this requirement.
Host a NodeJS on the primary domain where APIs and static routes will run. And Host a react app on a subdomain where the admin panel run.
so the domains would be as follows:
React path would be app.website.com
Node path would be website.com
A user can login via API in react app and store the token in cookies with the primary domain. Now we can verify JWT in static routes by retrieving that token from the cookie.
Store a token in cookie:
document.cookie = `token=${TOKEN};expires;;domain=${PRIMARY_DOMAIN};path=/`;
Middleware to check the authentication:
const { token } = req.cookies
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
if (err) {
return res.status(401).send("Unauthorized!");
}
req.user = decoded;
});
next()

Auth0 authentication of single-page-app on a different domain than the api

I'm trying add Auth0 authentication to my single-page-app. My app is running under a domain, say app.mycompany.com, whereas the api used by this app is running under a different domain, say api.mycompany.com.
I'm aware of this thread:
Single Sign On (SSO) solution/architecture for Single Page App (SPA)
and the auth0 articles and github repositories linked by here. But I have a feeling that my scenario is slightly simpler, as I don't necessarily want to have single-sign-on between several different single-page-apps. For a start I just want the seperation between the API and the app.
Here is what I have tried already:
I already started from the article React Login With Auth0 and downloaded the starter project. I can surely login without problems and it will leave me with an id_token in my localStorage containing a JWS issued by Auth0.
I can also login directly on api.mycompany.com (my FeathersJS API application) and I can see that during the OAuth redirecting process, the id_token token is magically translated to a feathers-jwt token issued by my Feathers application containing the internal ID of the user-object matching the auth0-ID. I also have implemented the logic used to map from the Auth0-ID to my internal ID. Furthermore all my Feathers hooks such as validation of token and population of the user are working.
What I cannot figure out is how to alter the react-application running under app.mycompany.com with an Auth0-token in localStorage, so that this token is translated to a feathers-jwt token by api.mycompany.com, in such a way that all succeeding API-calls automatically has the feathers-jwt token included so the API can validate the user and return the right data.
Any suggestions on how to proceed will be greatly appreciated.
A couple of more background details:
The api is built on node.js and featherjs (which basically is an extension of Express)
The single-page-app is built on ReactJS and is served by a simple Express server, but it could be served by any server that can serve static files over http. The single-page-app makes http-requests to the api to read data and perform operations.
The api has the following lines of code taking care of the authentication:
const authentication = require('feathers-authentication');
const Auth0Strategy = require('passport-auth0').Strategy;
app.configure(authentication({
local:false,
token: {
secret: 'mysecret',
payload: ['email', 'auth0Nickname'],
issuer: 'mycompany'
},
idField: 'id',
shouldSetupSuccessRoute: false,
auth0: {
strategy: Auth0Strategy,
domain: 'mycompany.eu.auth0.com',
'clientID': 'xxx',
'clientSecret': 'yyy'
}
}));
I had exactly the same problem as you, I wanted to authenticate a user from a single page application, calling the API located on an other server.
The official auth0 example is a classic Express web application that does authentication and renders html page, but it's not a SPA connected to an API hosted on an other domain.
Let's break up what happens when the user authenticates in this example:
The user makes a request calling /auth/auth0 route
The user is automatically redirected to the Auth0 authentication process (Auth0 login form to choose the provider and then the provider login screen)
The user is redirected to /auth/success route
/auth/success route redirects to the static html page public/success.html, also sending a jwt-token cookie that contains the user's token
Client-side, when public/success.html loads, Feathers client authenticate() method reads the token from the cookie and saves it in the local storage.
From now, the Feathers client will authenticate the user reading the cookie from the local storage.
I tried to adapt this scenario to a single-page application architecture, implementing the following process:
From the SPA, call the authentication API with a source query string parameter that contains the SPA URL. For example: http://my-api.com/auth/auth0?source=http://my-spa.com
Server-side, in /auth/auth0 route handler, create a cookie to store that URL
After a successful login, read the source cookie to redirect the user back to the SPA, sending the JWT token in a cookie.
But the last step didn't work because you can't set a cookie on a given domain (the API server domain) and redirect the user to an other domain! (more on this here on Stackoverflow)
So actually I solved the problem by:
server-side: sending the token back to the client using the URL hash.
client-side: create a new html page that reads the token from the URL hash
Server-side code:
// Add a middleware to write in a cookie where the user comes from
// This cookie will be used later to redirect the user to the SPA
app.get('/auth/auth0', (req, res, next) => {
const { origin } = req.query
if (origin) {
res.cookie(WEB_CLIENT_COOKIE, origin)
} else {
res.clearCookie(WEB_CLIENT_COOKIE)
}
next()
})
// Route called after a successful login
// Redirect the user to the single-page application "forwarding" the auth token
app.get('/auth/success', (req, res) => {
const origin = req.cookies[WEB_CLIENT_COOKIE]
if (origin) {
// if there is a cookie that contains the URL source, redirect the user to this URL
// and send the user's token in the URL hash
const token = req.cookies['feathers-jwt']
const redirectUrl = `${origin}/auth0.html#${token}`
res.redirect(redirectUrl)
} else {
// otherwise send the static page on the same domain.
res.sendFile(path.resolve(process.cwd(), 'public', 'success.html'))
}
})
Client-side, auth0.html page in the SPA
In the SPA, I created a new html page I called auth0.html that does 3 things:
it reads the token from the hash
it saves it in the local storage (to mimic what the Feathers client does)
it redirects the user to the SPA main page index.html
html code:
<html>
<body>
<script>
function init() {
const token = getToken()
if (!token) {
console.error('No auth token found in the URL hash!')
}
// Save the token in the local storage
window.localStorage.setItem('feathers-jwt', token)
// Redirect to the single-page application
window.location.href = '/'
}
// Read the token from the URL hash
function getToken() {
const hash = self.location.hash
const array = /#(.*)/.exec(hash)
if (!array) return
return array[1]
}
init()
</script>
</body>
</html>
And now in the SPA I can use the Feathers client, reading the token from the local storage when the app starts.
Let me know if it makes sense, thank you!
If you haven't done so, you should follow this article (React Login with Auth0) to implement the authentication on your React application. If you already tried to follow it, update your question with specific issues you faced.
Even though you currently not need SSO, the actual implementation of the authentication in your application will not vary much. By using Auth0 enabling SSO across your apps is mostly enabling configuration switches.
Finally for a full reference with all the theory behind the security related aspects of your exact scenario check:
Auth0 Architecture Scenarios: SPA + API
Update:
The full scenario I linked too covers the most comprehensive scenarios where an API is accessed by a multitude of client applications that may even be developed by third-parties that do not own the protected API, but want to access the data behind it.
It does this by leveraging recent features that are currently only available in the US region and that at a very high level can be described as an OAuth 2.0 authorization server delivered as a service.
Your particular scenario is simpler, both the API and client application are under control of the same entity, so you have another option.
Option 1 - Leverage the API authorization through Auth0 US region only (for now)
In this situation your client application, at authentication time, would receive an id_token that would be used to know the currently authenticated user and would also receive an access_token that could be used to call the API on behalf of the authenticated user.
This makes a clear separation between the client application and the API; the id_token is for client application usage and the access_token for API usage.
It has the benefit that authorization is clearly separated from authentication and you can have a very fine-grained control over authorization decisions by controlling the scopes included in the access token.
Option 2 - Authenticate in client application and API in the same way
You can deploy your client application and API separately, but still treat them from a conceptual perspective as the same application (you would have one client configured in Auth0 representing both client-side and API).
This has the benefit that you could use the id_token that is obtained after authentication completes to know who the user was on the client-side and also as the mechanism to authenticate each API request.
You would have to configure feathers API to validate the Auth0 id_token as an accepted token for accessing the API. This means that you don't use any feathers based on authentication on the API, that is, you just accept tokens issued by Auth0 to your application as the way to validate the access.

PassportJS + RestAPI +SPA

I am using ExpressJS to build RestAPI, client is SPA and support authenticate by Google/FaceBook/GitHub/... via PassportJS. My question, callback from social login will return to RestAPI or SPA? If system returns to RestAPI, how can to redirect to home page on SPA. Another case, if system callback SPA, how can RestAPI receive and validate token from client. Please let me know common approachs.
Thanks,
You provide the callback url to the authentication service, you decide whether you handle the route by the SPA or the API. Oauth authentication (simplified) has two steps. Illustration on github:
Step 1) https://github.com/login/oauth/authorize?client_id=*YOUR_CLIENT_ID*$redirect_uri=*YOUR_REDIRECT_URI*
Opens a popup dialog that requests the user to authorize your application, if successful returns to your redirect_uri with a query parameter ?code=AUTHORIZATION_CODE
Step 2)You exchange the above AUTHORIZATION_CODE for a long-term access token via https://github.com/login/oauth/access_token
In your architecture, you should do Step 1 in the SPA and Step 2 in the rest api. You should rely on the spa to get the authorization code from the authentication provider, send that to your rest api, let the rest api exchange for a long term access token, save that token to the database, use it to retrieve user information or do whatever you want with it, then log in the user.
For step 1, you only need the CLIENT_ID, for step 2 CLIENT_ID and CLIENT_SECRET as well, so you can keep your application secure by storing the CLIENT_SECRET on the server side only.
The problem with having the callback uri handled by your rest api, is that the callback uri is called by the authentication provider (in this case github) and not by your SPA, therefore you can't send a response that redirects the user to the homepage. This would only work if your templates and routing were handled on the server side, which I assume is not the case in your architecture.
It's not obvious from the documentation, but when you register a passport middleware on a route like app.post('/login',
passport.authenticate('github'),, the middleware will check if the 'code' query param contains an AUTHORIZATION_CODE, if not, it kicks off step 1, if yes step2.
I used same stack(express, angular, passport) and followed that approach.
I created a button.
Login with facebook
Also I have two routes for passport
// send to facebook to do the authentication
app.get('/auth/facebook', passport.authenticate('facebook', {scope: 'email'}));
// handle the callback after facebook has authenticated the user
app.get('/auth/facebook/callback', passport.authenticate('facebook', {
successRedirect: '/#/profile',
failureRedirect: '/' //Redirect Homepage
}));
This code shows that if you login successfully you will be redirect to angular route(/#/profile) After redirect you'll have a cookie which has a token with name connect.sid since passportjs uses express-session.
Then you can check if user logged in everywhere by this middleware
// route middleware to ensure user is logged in
function isLoggedIn(req, res, next) {
if (req.isAuthenticated())
return next();
res.redirect(301, '/');
}
You can take a look at my repository which contains the code above.
https://github.com/AOnurOzcan/meanTest
If you encounter a problem please let me know.

Node.js Express - sharing Passport sessions on different servers

So we have a "web" server and an API server - the web server serves up HTML and the API server serves up JSON.
We use Passport's Twitter strategy to authenticate on the web server.
My question is - what is the best way to check on the API server that the user who has authenticated with the web server is also authenticated with the API?
My assumption is that we should put most of the Passport code into the web server, have the user authenticate with it as usual with Passport, and use some middleware in the API server like so to check if the user is logged in (has a session):
app.use(passport.initialize());
app.use(passport.session());
app.use(expressSession({
secret: 'keyboard cat',
store: new MongoStore({mongooseConnection: mongoose.connection})
}));
app.use(function(req,res,next){
if(req.isAuthenticated()){
next();
}
else{
console.log('unauthenticated request');
res.status(403).json({error:'unauthorized request'});
}
});
however, the above doesn't seem to be enough. I am utterly confused about exactly what code I need on the API server - I believe I need to read from the same session store that the web server writes to and to look at a token in the request to the API server and compare it with a token in the session store?
I am not sure I understand what the req.isAuthenticated() function does? It seems like I now need to write my own req.isAuthenaticated() function which reads from the session store asynchronously...
Does anyone have an example of how to do this right?
You might be able to do as you said - authenticate using the web server and just verify that they are authenticated using the API.
Providing that both servers share the same remote session store, and both endpoints have access to the express session cookie, you should be able to implement something like this for the API:
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated())
return next();
else
// Not authenticated
}
Or
if (req.user) {
// logged in
} else {
// not logged in
}
As middleware (as you showed in your example), or on a per-route basis.
This may not be viable if you can't access the same, shared, session cookie though. Other options may be available depending on what structure your app is built in ie. - if the web server is speaking directly to the API.
You can read the raw req.isAuthenticated function from the passport github.

Web application authentication strategies

I'm looking for some advice with authentication for my web app. I'm using Node, Express and Passport to build out this app
The app has a REST API using Basic Auth (no session creation), and hosts several Angular.js web pages using form Auth (with session creation).
I would like the Angular pages to connect to the REST API, which is using a different Auth strategy. It seems I have two options:
Create a custom Basic Auth middleware, (because Passport doesn't do this out of the box). This will do session Auth if request has one, otherwise standard Basic Auth
Expose two API's one with Basic Auth (for external use) and one with form Auth (for the app pages)
If also heard that using OAuth2 might be an option, but surely that only makes sense for authenticating with a third party?
My current solution has been to perform mixed auth (session and basic) on the rest api. If a session exist continue, otherwise perform basic auth. As follows:
api.coffee:
app.api.external.get("/agents", [auth.basic], (req, res) ->
res.json myListOfAgents
auth_middleware.coffee
basic: (req, res, next) ->
if req.isAuthenticated()
return next()
else
return passport.authenticate('basic', { session: false })(req, res, next)

Resources