NodeJS+Express: Passport vs OAuth2 in relation to scope (access permissions) - node.js

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?

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.

How to retrieve, save and renew idToken from google Passport

I am using google oauth passport strategy in my nodejs app and I don't know the right way to store and renew my idToken, so I can use it for authentication purposes when making api calls to an external api. Presently, I'm writing the token to a textfile and reading it from there. I feel this is a bad idea and just need suggestions on how to do it in a more elegant way.
module.exports = function (passport) {
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: url },
async (accessToken, refreshToken, params, profile, done) => {
const idToken = params.id_token
fs.writeFile('token.txt', idToken, err => {
if (err) {
console.error(err)
return
}
//file written successfully
console.log("file written successfully")
})
}
As another poster indicated, if you are using the token purely for login, you may not need to store and renew the ID token at all.
However, if you are using the token to access Google APIs, and you need access even without the user present, the most common approach is:
Store the access token and refresh token encrypted in a database, both associated with the user
When using the access token, check for an error response from Google that indicates that the token has expired, use the refresh token to get a new access token, and save that new access token in your database (again, encrypted)
In Google's case, be sure to pass the access_type=offline parameter to retrieve an refresh token. Also, if this is a user you have previously authorized, you'll need to pass prompt=consent, as otherwise Google will not issue a refresh token.
An alternative to implementing all of the above is to use a managed OAuth service like Xkit, where I work. It handles storing, encrypting, and refreshing tokens, and all of the provider-specific nuances. Again, only useful in cases where you're using the tokens to access APIs (and not for login), but another option to consider.

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.

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);
}); } ));

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