Web application authentication strategies - node.js

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)

Related

Handle Google OAuth with JWT (react + nodejs)

I am working on the authentication system of a web app, using Next.js for the client app and Node.js for the API.
I have my Next.js app on port 3000
I externalized the API of my application, on port 5000
That's why I used JWT for the local signin/signup strategies.
(I'm planning to use the same API for the mobile application later)
I am now wondering what is the best approch for a Google Authentication.
I have set it up, but I don't know how to give the token to the client.
Here is the process:
On Signin page (http://localhost:3000/signin), the user clicks on "Google authentication". It redirects to 'http://localhost:5000/auth/google"
Passport handles it, it redirects to Google OAuth page. User authorize the application.
Google redirects to the callback URL (http://localhost:5000/auth/google/redirect)
In the callback route, I can create a JWT. But how can I give it back to the client ?
I have thought of passing it through URL, but I am wondering if it is safe ?
Is there another way to do it / Am I missing the point ?
router.get('/google/redirect', (req, res, next) => {
return passport.authenticate('google', (err, user) => {
if (err) {
return res.redirect('http://localhost:3000/signin')
}
console.log(user)
// Create JWT and redirect to http://localhost:3000/signin/oauth?token=xxx ?
})(req, res, next)
})
I can show more code if needed, but it works (code is not the blocking point).
Thank you in advance !
all you have to do is setting up cookie session. When google sends responds to /google/redirect, passport.authenticate will call req.login() and this will call the serializeUser
passport.serializeUser(
(user, done ) => {
done(null, user.id); // stores the id<4kb
}
);
this function will create, passport:{user:userId}. this is the unique identifying information about the user. This where you need session. Because passport.js will automatically look for req.session and attaches the passport object to the req.session.
Since we are storing only userId, usually cookie-session package. this package will set req.session object, passport.js will attach the passport object and the cookie-session will store this on the client.

How to include acr_values in Passport authenticate

I have Passport set up and working using an oauth2 strategy (code flow) against Identity Server 4. I need to include acr_values in the call to the Identity Server (this is working from another client correctly).
My understanding (as shown here for example) is that the following code should add acr_values to the auth call:
app.get('/auth', (req, res, next) => {
passport.authenticate('oidc', { acr_values: 'urn:grn:authn:fi:all' })(req, res, next);
});
But this doesn't work. Running the code from that sample also doesn't work.
Is this something I'm doing wrong, or has it changed?
How can I have passport include acr_values in the call to the Identity Server?
The acr_values format you use is not valid, it must be a single string which is space separated like: acr_1 acr_2 tenant:tenant_value idp:idp_value
Per Docs:
acr_values
allows passing in additional authentication related information - identityserver special cases the following proprietary acr_values:
idp:name_of_idp bypasses the login/home realm screen and forwards the user directly to the selected identity provider (if allowed per client configuration)
tenant:name_of_tenant can be used to pass a tenant name to the login UI
Here is where in code it gets validated: https://github.com/IdentityServer/IdentityServer4/blob/main/src/IdentityServer4/src/Validation/Default/AuthorizeRequestValidator.cs#L771
Example in tests: https://github.com/IdentityServer/IdentityServer4/blob/main/src/IdentityServer4/test/IdentityServer.IntegrationTests/Endpoints/Authorize/JwtRequestAuthorizeTests.cs#L667

Express application for Authentication as seperate microservice

Current Situation:
I am currently working with a specific oauth provider and i am hosting my applications as microservices in a kubernetes cluster.
My end user is actively working with an angular application hosted as a docker container using nginx as webserver.
Now my idea was to integrate the authentication as a seperate microservice using node.js express and passport. so The workflow would be
User hits login in angular and gets redirected to the express application (same host address just a different endpoint /auth/someProvider)
The express application has no user interface it just handles all the oauth redirecting and communication with the provider, after the user information has been collected it redirects back to the angular application.
Now this works pretty for one last part. When my /auth/provider/callback would redirect inside of the express application it is very easy to access the request object that has been extended with the user object. when I redirect to an external website I get the cookie and everything but not an easy way to access the user object.
My acutal question(s):
Is there a safe way to pass that user information from the Request object directly to be used by the angular application (Best way i could think of is to use the headers as they are encrypted as well in https but still seems kind of hacky).
Is ita good idea in general to use OAuth that way.
The big advantage to this solution would be that I could use the same Docker Container with many web projects not having to implement Authentication one by one by just changing ClientId and Secret Env Vars in that Docker Container.
OK this is how I made it work.
basically you implement the basic concept described in the passport.js Documentation and add a seperate endpoint to access the userinfo. This is why I will describe where it starts to differ a bit
Step 1: Authenticate user at the Gateway
As described in the passport.js documentation. The authentication microservice needs the following callback (The router here is already served under /auth)
router.get("/provider/callback", passport.authenticate("provider", {
failureRedirect: "https://your-frontent-app.com/login",
}), (req, res) => {
res.redirect("https://your-frontend-app.com");
});
When you get back to your Web-Application you will see the Session-Cookie has successfully been stored.
Step2: Get User Info from Endpoint
Now we need the second endpoint /auth/userinfo in our routes.
router.get('/userinfo', (req, res) => {
if(!req.session) return res.status(401).send();
if(!req.session.passport) return res.status(401).send();
if(!req.session.passport.user) return res.status(401).send();
return res.json(req.session.passport.user).status(200).send();
});
Not very pretty this block with the 3 if's but it happened to me that all of those combinations could be undefined
Now, with the session cookie stored in our browser we can call that endpoint from our front-end with the credentials (I'll use axios for that)
axios.get('https://your-authenticator.com/auth/userinfo', {withCredentials: true})
.then(res => {
//do stuff with res.data
});
Now theres one more thing to note. If you want to use credentials to call that API setting the Access-Control-Allow-Origin Header to * will not work. You will have to use the specific host you'll be calling from. Also you will need to allow the Credentials in the Header. So back in your main Express app make sure you use the headers like
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "https://your-frontend-app.com");
res.header("Access-Control-Allow-Credentials", "true");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Auth, Authentication, Authorization, Credentials");
next();
});

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.

Resources