Invitation system using Passport JS - node.js

I'm rewriting an authentication system to be OAuth only with Passport JS. I've designed a user flow as described below, but I can't see where the best point to get Passport to interact with information from the original request is.
The flow will be:
Authenticated user sends invitation to a new user's email address
New user clicks on link and lands on invitation page (/invitation/SOMECODE)
Invitation code is verified and, if still valid, allows user to auth via Google/Slack
New profile is created in the Strategy, but associated with the existing company (instead of creating a new one)
I'm looking to get access to the req.params inside of the Google Strategy, because this is the point I would typically create a new profile, and company for first time users. But if there's an invitation code, I want to do lookups on that info at this point.
I can't see any documentation that supports this approach, other than Node Passport invitation strategy which uses a password after initial sign up.
Can you get access to req object inside the strategy or is there a better way to approach this with another middleware?

I think what you're looking for is passReqToCallback
Example from docs:
passport.use(new TwitterStrategy({
consumerKey: TWITTER_CONSUMER_KEY,
consumerSecret: TWITTER_CONSUMER_SECRET,
callbackURL: "http://www.example.com/auth/twitter/callback",
passReqToCallback: true
},
function(req, token, tokenSecret, profile, done) {
if (!req.user) {
// Not logged-in. Authenticate based on Twitter account.
} else {
// Logged in. Associate Twitter account with user. Preserve the login
// state by supplying the existing user after association.
// return done(null, req.user);
}
}
));
See passReqToCallback in the docs: http://passportjs.org/docs/authorize

Related

Using the correct oauth2 credentials to connect to AzureDevops through NodeJS using Passport.js

I am having issues to figure out what exact credentials I have to use to connect to Azure Devops through NodeJS using Passport.js.
I am using the strategy as described here: http://www.passportjs.org/packages/passport-azure-oauth2/
In AzureDevops, I have created an Application under the: Authorized OAuth Apps, and then have the following information at my disposal there:
This is the current code I use to create the strategy:
passport.use("azure-devops", new AzureOAuth2Strategy({
clientID: process.env.AZURE_DEVOPS_CLIENT_ID,
clientSecret: process.env.AZURE_DEVOPS_CLIENT_SECRET,
callbackURL: process.env.AZURE_DEVOPS_CALLBACK_URL,
resource: process.env.AZURE_DEVOPS_RESOURCE,
tenant: process.env.AZURE_DEVOPS_TENANT,
prompt: 'consent',
state: true
},
function (accessToken, refreshtoken, params, profile, done) {
var user = jwt.decode(params.id_token, "", true);
done(null, user);
}));
passport.serializeUser(function(user, done) {
done(null, user)
})
passport.deserializeUser(function(obj, done) {
done(null, obj)
});
app.use(passport.initialize())
app.use(passport.session())
Now what I do not understand is which information to fill into the respective credentials fields. Especially confusing to me are the fields "tenant" and the field "resource". Passport.js describes these fields as optional, however, if I leave the "tenant" field out, when I go to the microsoft login page using the /auth/login route, I am getting into a loop. It means that when I enter my email and password, it redirects me back to the login and this is endless. If I add the "tenant" field with an ID (where I am not sure of which ID I need to use).
I receive the following error:
What am I supposed to add to these fields in the Strategy and where do I find this information???
Also confusing to me is that after I have added my application and I go to the dashboard about added applications, it says I have not granted access to any application yet, although I have added that application before ... it looks like this on my side:
What am I doing wrong here overall?
Passport-azure-oauth2 strategy is used to authenticate to access Azure Resources not Azure devops. This strategy is actually the Azure OAuth 2.0 client credentials flow.
If you want to use Oauth2 as authentication for azure devops. You should use OAuth2 authorization code grant flow. See below document for more information.
Authorize access to REST APIs with OAuth 2.0. There is a sample project you can check out.
There are many ways to authenticate your application with Azure DevOps Services. Check out this document for more examples.
As for Passport.js, you can check out OAuth2orize which is a sibling project to Passport, provides a toolkit for implementing OAuth 2.0 authorization servers.

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.

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?

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.

Create vs Login when using Google oauth for access

I am currently trying to setup my server to allow users to login with google oauth 2.0.
I am using passport and passport-google-oauth.
Normal set up is something like:
var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
passport.use(new GoogleStrategy({
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
callbackURL: "http://127.0.0.1:3000/auth/google/callback"
},
function(accessToken, refreshToken, profile, done) {
User.findOrCreate({ googleId: profile.id }, function (err, user) {
return done(err, user);
});
}
));
However what I really want is to still control access to my server after accounts are approved.
Meaning a user would first 'create' and account using google, then be able to signin once there account is approved.
I would really like there to be a signup route and login route:
app.get('/auth/google/signup',
passport.authenticate('google', { scope: ['profile', 'email'] }));
app.get('/auth/google',
passport.authenticate('google', { scope: 'https://www.googleapis.com/auth/plus.login' }));
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/login' }),
function(req, res) {
// Successful authentication, redirect home.
res.redirect('/');
});
My problem is that when I get to the GoogleStrategy setup I don't really know which route they initially hit. IE if they hit the login route but had not created an account I do not want to create an account I want to warn them that they did not yet create an account. Had they hit the signup route and already had an account I would not want to create another account I would just tell them they already have an account.
Is there anyway in the GoogleStrategy that I can tell which route the user initially hit on my server?
In your user model create the "approved" field, with default False (Boolean)
And you can check this field on the GoogleStrategy to restrict the access.
If you want to apply this on all Strategies you can filter on the serialization method in passport.
Hope it helps.
You can pass a 'state' query parameter in your initial request that will be round-tripped back to your callback.
Documented here:
https://developers.google.com/identity/protocols/OAuth2WebServer
state
Any string Provides any state that might be useful to your
application upon receipt of the response. The Google Authorization
Server roundtrips this parameter, so your application receives the
same value it sent. To mitigate against cross-site request forgery
(CSRF), it is strongly recommended to include an anti-forgery token in
the state, and confirm it in the response. See OpenID Connect for an
example of how to do this.

Resources