Passing OAuth user data back to React via Node (Passport) authentication - node.js

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.

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.

OAuth2 callback - identify user?

The question: when I authenticate a user using oauth2 (initiated from my server), how do I get the initial user id from the oauth2 callback so I can map it back to the initial auth request on my server?
Contex:
I'm working on a web app where I need to ask user to grant access to their google calendar.
Consider the oauth flow:
web client (sends request to) -> backend (sends oauth request to) -> google (grants access) -> backend (how do I know the user in this step to store the refresh_token?)
Here is more details default flow:
user logs in to my web app (client)
the web app asks the user to start oauth2 flow (client)
that sends the "start auth flow" request to my backend (backend)
on the backend I send oauth request to google like below:
const authUrl = new google.auth.OAuth2(clientId, secret, redirectUrl)).generateAuthUrl(options)
res.redirect(authUrl)
this redirects user to the google consent page. (google)
Once the user granted the permission, they are redirected back to the url specified in OAuth2 client (backend, my "callback" endpoint)
at this point I need to save the refresh_token to the user's database location. (backend)
So how do I understand from the "callback" that this is still the same user who started the flow?
It appeared that there is a parameter called state that one can use to pass specific data and google will return it back. This solves my question since I can pass user id or my session token as state and read it in the callback.
From the doc:
state Recommended.
Specifies any string value that your application
uses to maintain state between your authorization request and the
authorization server's response. The server returns the exact value
that you send as a name=value pair in the hash (#) fragment of the
redirect_uri after the user consents to or denies your application's
access request.
In case of nodejs google oauth2 library that may look like this:
oauth2ClientGlobal.generateAuthUrl({
access_type: 'offline',
scope: this.scopes(),
state: base64UserId // <- google will return this param back
});
You should keep the refresh token on the server in a database. This is the most secure way and prevents attacker from accessing the refresh token over the wire and using it to access their account.
Create an id for the user, use that id to map to client data on server such as refresh token. Pass that id back to the browser and client in cookies or JWT. Then, whenever user makes a request back to your server they will always pass that id you created in cookies. You can use that id to lookup the user in the database and get the refresh token.
Try using passportJs which provides authentication using third party authentication providers.
var GoogleStrategy = require( 'passport-google-oauth2' ).Strategy;
passport.use(new GoogleStrategy({
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
callbackURL: "http://yourdormain:3000/auth/google/callback",
passReqToCallback : true }, function(request, accessToken, refreshToken, profile, done) {
User.findOrCreate({ googleId: profile.id }, function (err, user) {
return done(err, user);
}); } ));

NodeJS+Express: Passport vs OAuth2 in relation to scope (access permissions)

Imagine I'm implementing an API service at api.example.com that needs to protect its endpoints using OAuth2 with sufficient granularity to accommodate different user types.
users who authenticate against Facebook can only access https://api.example.com/facebook
users who authenticate against Twitter can only access https://api.example.com/twitter
users who authenticate locally against the Authorization service can access both facebook and twitter endpoints
This means we have the following roles:
Authentication provider (Authorization server, Facebook, Twitter)
An Authorization provider (My OAuth2 server) (auth.example.com)
A Resource provider (My API server) (api.example.com)
An ExpressJS webapp or SPA (www.example.com), or native app
Questions:
Q1 How should the application flow be implemented?
My thoughts are ...
A webapp must store the accessToken to api.example.com in a session, and send the sessionid to the web browser. If there is no accessToken, it must redirect the browser to auth.example.com/login/selectprovider/, providing the client_id="www.example.com" and client_secret, and redirect_url="https://www.example.com/".
On https://auth.example.com/login/selectprovider/ the user selects facebook or twitter or local, and auth.example.com redirects the user (again) to /login or facebook or twitter, using the client_id and client_secret and redirect_url=https://auth.example.com/twitter or https://auth.example.com/twitter to easily distinguish the three.
Facebook/twitter/local methods will authenticate the user and redirect the browser back to auth.example.com.
auth.example.com will then generate a (Bearer) accessToken for api.example.com and store it together with the user.id into a local database table, with associated scope ("facebook", "twitter" or "local"), put the key (userId) in the browser session and redirect to https://www.example.com/
Now when the user clicks on a link in the WebApp, www.example.com does a GET or POST on api.example.com, providing client_id="www.example.com" client_secret and access_token.
The api.example.com endpoints verify the accessToken, and if ok, execute the API and return the value to www.example.com that renders it to the browser of the user.
Q2 Is above the correct flow, or is there a better way?
If correct, how does api.example.com validate the accessToken at its endpoints?
Should auth.example.com expose a special endpoint for this (eg. /userinfo) that can only be accessed with a client_id="api.example.com" and client_secret? That seems expensive to do for each call, but how else could api.example.com trust the validity of a token (because it expired or the user logged out)?
What's the OAuth2 standards compliant way?
Q3 In passport/OAuth2orize examples, I see 3 ways that authentication is validated:
if (req.user) {...};
if (req.isAuthenticated()) {...};
passport.authenticate('bearer', {session: false);
Q4 What does passport.authenticate('facebook') do exactly?
This information is provided in the strategy.
passport.use(new FacebookStrategy({
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET,
callbackURL: "http://localhost:3000/auth/facebook/callback"
},
function(accessToken, refreshToken, profile, cb) {
User.findOrCreate({ facebookId: profile.id }, function (err, user) {
return cb(err, user);
});
}
));
So this contains the client_id and client_secret. When is this used? Every time passport.authenticate('facebook') is called? Does do the whole redirect to facebook login page, get Authorization code grant and exchange it for an accessToken, store the accessToken internally somehow and keep it until a logout?
It seems unlikely but I don't see how it can actually authenticate the user without all this. And it would be too expensive to do for every API call, so I'm sure it works more efficiently. But I don't know how and studying the source code didn't make it more clear.
Q5 How does *api**.example.com know when the Bearer accessToken included by www.example.com in every API request is not forged or expired?
I assume it has to check it against the auth.example.com service, and that service in turn must check if the facebook/twitter session is still valid too, and refresh tokens using the refreshToken at that moment?

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.

Getting the domain name making requests to my API endpoints

I'm using Node + express to build an API. The idea is to be able to let other developers login and register their app so that i can authorize access to my API endpoints.
exports = passport.use(new FacebookStrategy({
clientID: '999999999',
clientSecret: '999999999',
callbackURL: "http://localhost:3000/auth/facebook/callback"
},function(accessToken, refreshToken, profile, done) {
profile.access_token = accessToken;
db.mongoClient.connect(db.moments, function(err, db){
var user = db.collection('user');
user.find({'facebook':profile.id}).toArray(function(err, docs){
console.log(docs);
})
})
done(null, profile);
}));
I have set this up using the passport facebook strategy. This allows developers to login to a profile page on my app where they are presented with the access_token i got from facebook. I'm using this access_token to allow a connection between my app and their app using the bearer token strategy.
However, i also want to add another layer of security. They should register the domain name that is going to make API calls to my app. This should protect me from developers passing along the token to other developers (did i got that part right?).
The question: How can i check that they are indeed making the request from the registered domain name?
thx,
After your facebook authentication, you know user and you know his registered domain. Then make call from server to some defined script at registered domain. And if indeed it was call from this user then he should return some response which you agreed with him. And if response is ok, then finish authentication (if I correctly remember some payment systems make this verification for online shops).
Or something more sophisticated, after facebook auth, send to registered domain url some temporary token. Then user must send you in next call this temporary token and you exchange it for final token.
To be secure your users should have https when your server call them. Otherwise it is not reliable.

Resources