Handle Google OAuth with JWT (react + nodejs) - node.js

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.

Related

Correct security implementation strategy for multi-provider authentication and api route protection with Nodejs

We have a small React Web application with a Fastify backend (but can very easily apply the same concepts to Express js) that feeds the db data to the frontend clients.
Some of the API routes are protected by means of middleware checking the validity of the access token.
Our auth strategy used to be only credentials based, ie using Username and Password.
We have recently implemented authentication by Google and Facebook as well and for the most part, they seem to work well.
We now want to replicate our web application to a mobile application.
I have never written a mobile app and am currently busy learning React-Native.
I managed to implement Google Auth on the IOS app.
Here is the problem. I am not sure how to implement token validation for mobile clients on protected routes using the Google access token. I am however able to successfully implement validation if I use the id token.
My understanding is that validation on protected routes should be done using the access token, otherwise, what is the point of the access token in the first place.
Anyway, my understanding of the entire authentication flow is clearly flawed.
I just need some advice, samples or references to articles that I can read that can clarify the concepts to me.
Just the summary of what I hope to achieve:
a single backend with various (but not all) protected routes
the backend to support credential-based, Google and Facebook authentication strategies
backend middleware to check the validity of the access token
backend should serve both native mobile and web applications.
Sample of a protected route:
fastify.get(
"/protectedroute",
{ preValidation: [fastify.googleverify] }, //<--- Middleware only checking Google validity currently. TODO: Multi-provider middleware to be created
async (request, reply) => {
reply.code(200).send({ message: "authenticated", user: request.user });
}
);
Sample of middleware to check google id_token validity:
fastify.decorate("googleverify", async function (request, reply, done) {
if (!request.raw.headers.authorization) {
return done(new Error("Missing token header"));
}
const bearer = request.raw.headers.authorization;
const token = bearer.split(" ");
try {
const userinfo = await verify(token[1]);
request.user = userinfo;
return done();
} catch (error) {
reply.code(401).send({ message: "Not Authorized", error: error });
}
});

How to authenticate angular 10 client app from node/express js using passport-google strategy?

I'm building a web app that is being used on top of microservices architecture.
Using node/express js I have implemented auth service and products service both are listening on different ports like
http://localhost:8001 for authentication service
http://localhost:8002 for products service.
Kong Gateway used to authenticate and connect the microservices with jwt. Implemented passport-jwt and passport-local strategy to authenticate the users from client side using post calls.
Finally I have implemented the google auth on server side using passport-google strategy in this below URL
http://localhost:8001/auth/google -> it directs me to google auth consent screen after sign in it is redirecting to below Url
http://localhost:8001/auth/google/callback with token. it works fine at server end.
async googlecallback(req, res, next){
passport.authenticate('google', {
session: false,
}, (err, user, message) => {
if (!user) {
return next(new UnAuthorizedException(message))
}
const token = user.generateToken()
user = UserTransformer.transform(user)
user.token = token
this.Response(res, user, message) // sending response to client using custom method
})(req, res)
}
. When I come to authenticate the user from angular app client side. I'm unable to proceed further. just struggling here. :-(
How can I authenticate the user when they click google sign in button in angular 10 on client side?
My front end app Url like http://localhost:4002/account/login
Tried to use window.open("http://localhost:8001/auth/google","_blank") method, not working as expected.
res.setHeader('x-code', 'jwthere'); header method. Also tried to pass the JWT token with URL parameter. but both seems unsecure.
http://localhost:4002/account/login?token=7wF8bit5W1Pfi5Glt1X8H0YQu8BN7OeNRcX1zbj3AGpUHaYSxLlNIjHpzuw
security is the major concern here. I want the google sign in like khanacademy social login
https://www.khanacademy.org

Passing OAuth user data back to React via Node (Passport) authentication

I'm a little confused about the flow of data in a React application that authorizes a user through a third party OAuth provider. Right now I have a login button in React that directs to a Node server.
Log In
This directs to an Express route...
router.get('/auth/google',
passport.authenticate('google', {
session: false,
scope: ['email']
}));
that uses Passport to verify the login request.
passport.use(new GoogleStrategy({
clientID: process.env.OAUTH_CLIENT_ID,
clientSecret: process.env.OAUTH_SECRET,
callbackURL: "http://localhost:5000/api/auth/google/callback"
},
function (accessToken, refreshToken, profile, cb) {
return cb(null, profile);
}
));
The callback is set up to redirect the user back to the React application.
router.get('/auth/google/callback',
passport.authenticate('google', {
session: false,
failureRedirect: 'http://localhost:3000'
}),
function (req, res) {
// Successful authentication, redirect home.
res.redirect('http://localhost:3000');
});
This method allows me to either add a user to my DB via my Node app, or confirm the existence of the user already in my DB, and that's as far as I've seen any tutorial take it, but it seems entirely useless without the ability to send this information back to React. The point of logging in, at least in my opinion, is to have my application function specific to a user.
What I'd like to be able to do is send back a signed JWT token. Either one signed manually with my server, or the access token that passport gets from Google, either will do. As well, I'd like to send a user ID. I need this so my app can make API calls with the user ID as part of the request to protected routes, thus the need for the JWT.
It's confusing to me because without this exchange of data, the purpose of OAuth seems essentially useless, so I must be missing a vital step in the process, or I must be looking at the problem from the wrong perspective. Where or how should I be informing my React application of the details of the logged in user, and give them a token for local storage? As far as I can tell, there's no way to send package of data back with the callback URL.
I found a way, you can use eventsource for this. Check this article for more information https://www.blog.tericcabrel.com/implement-server-sent-event-in-node-js/
You can send you token as query param in your frontend app
res.redirect(`http://YOUR_FRONTEND_HOST:3000/?token=` + "Your jwt token")
So in the frontend you can retrieve the token with a useEffect then make it disappear to don't make it avalible to the user.

Node.js SAML implementation with OneLogin

I am looking to setup our application in the application catalog of OneLogin, thus I need to create a SAML integration, as I understand it. I do see that they have toolkits available for this, but I am working in Node.js and there is no toolkit for that environment.
I have been reading their documentation as well as other posts and think that the process is something like the following:
1) Make a request to OneLogin to create the application and add it to their catalog.
2) My application needs to have a route point that I will provide to OneLogin which will be used as the redirect when someone clicks the icon for our app.
3) A user clicking on the icon for my app in the catalog will tokenize the user and send that to my defined route point with the information passed as a SAML request / XML.
4) My route point will need to consume the SAML request / XML and then will perform my internal login process. The information passed to my route point by OneLogin will include the necessary information for my site, like first name, last name, and email address. I will then do my internal application with that information and if it validates to an existing user, I would count that as a successful login and then let them continue. If they are not an existing user, I would send them through a user creation type form, but could default information from the SAML request / XML from OneLogin, or could just automatically create the user.
Does that seem like I have a high level understanding of the process?
Does anyone have examples in Node.js?
I was going to use the passport-SAML package.
Yes you're on the right track.
Passport-SAML works well for Express apps https://github.com/bergie/passport-saml
Your Passport SAML Strategy configuration should look something like this.
passport.use(new SamlStrategy(
{
path: '/login/callback',
entryPoint: 'https://{SUBDOMAIN}.onelogin.com/trust/saml2/http-redirect/sso/{APP_ID}',
issuer: 'passport-saml'
},
function(profile, done) {
console.log(profile);
return done(null, profile);
})
);
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(obj, done) {
done(null, obj);
});
Be sure to use the SLO Endpoint that is provided when configuring your app via the OneLogin portal.
Then setup your routes to use Passport
// Initiates an authentication request with OneLogin
// The user will be redirect to OneLogin and once authenticated
// they will be returned to the callback handler below
app.get('/login', passport.authenticate('saml', {
successReturnToOrRedirect: "/"
}));
// Callback handler that OneLogin will redirect back to
// after successfully authenticating the user
app.post('/login/callback', passport.authenticate('saml', {
callback: true,
successReturnToOrRedirect: '/users',
failureRedirect: '/'
}))
You also need to make sure you have set the ACS (Consumer) URL to your apps callback url and that the user you are testing with has access to the app.

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.

Resources