What is the best practice to use Oauth2, React, Node.js and Passport.js to authenticate user with Google sign on button? - reactjs

I want to have a login button in my website so when a user clicks on it, the user can use their Google credentials. I'd like to ideally perform the authentication server side using Express.js and Passport.js.
I implemented authentication server-side but the problem is that I can't make an AJAX request from the website to the server to start authentication because Google or Oauth don't support CORS. So I need to use a href element in my website which would call the server authentication endpoint. However, I can't catch server response in this way.
If I perform the authentication client-side (I'm using React) I could store login state in Redux and allow the user to access the website's resources. However, when the user logs out I need to make sure that server endpoints stop serving the same user which feels like implementing authentication twice: client-side and server-side.
In addition when authenticating client-side, Google opens a popup for the user to authenticate which I think is worse user experience then just a redirect when authenticating server-side.
I'm wondering what the best practice in terms of authenticating using Oauth2/Google. For example, stackoverflow.com also has Google button but just makes a redirect, without any popup, so I guess they figured out a way to perform server-side authentication and to bypass CORS issue.

I faced the same issue. This article is Gold link
1.In auth route File I had following code
const CLIENT_HOME_PAGE_URL = "http://localhost:3000";
// GET /auth/google
// called to authenticate using Google-oauth2.0
router.get('/google', passport.authenticate('google',{scope : ['email','profile']}));
// GET /auth/google/callback
// Callback route (same as from google console)
router.get(
'/google/callback',
passport.authenticate("google", {
successRedirect: CLIENT_HOME_PAGE_URL,
failureRedirect: "/auth/login/failed"
}));
// GET /auth/google/callback
// Rest Point for React to call for user object From google APi
router.get('/login/success', (req,res)=>{
if (req.user) {
res.json({
message : "User Authenticated",
user : req.user
})
}
else res.status(400).json({
message : "User Not Authenticated",
user : null
})
});
2.On React Side After when user click on button which call the above /auth/google api
loginWithGoogle = (ev) => {
ev.preventDefault();
window.open("http://localhost:5000/auth/google", "_self");
}
3.This will redirect to Google authentication screen and redirect to /auth/google/callback which again redirect to react app home page CLIENT_HOME_PAGE_URL
4.On home page call rest end point for user object
(async () => {
const request = await fetch("http://localhost:5000/auth/login/success", {
method: "GET",
credentials: "include",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"Access-Control-Allow-Credentials": true,
},
});
const res = await request.json();
//In my case I stored user object in redux store
if(request.status == 200){
//Set User in Store
store.dispatch({
type: LOGIN_USER,
payload : {
user : res.user
}
});
}
})();
5.last thing add cors package and following code in server.js/index.js in node module
// Cors
app.use(
cors({
origin: "http://localhost:3000", // allow to server to accept request from different origin
methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
credentials: true // allow session cookie from browser to pass through
})
);

Your authentication should be done server side. Here is how it works.
You make a fetch or axios call to your authentication route.
Your authentication route sends a request to Google's Authentication servers. This is important to have on the backend because you will need to provide your clientSecret. If you were to store this on the frontend, it would make it really easy for someone to find that value and compromise your website.
Google authenticates the user and then sends you a set of tokens to your callback url to use for that user (refresh, auth, etc...). Then you would use the auth token for any additional authorization until it expires.
Once that expires, you would use the refresh token to get a new authorization token for that client. That is a whole other process though.
Here is an example of what that looks like with Passport.js: https://github.com/jaredhanson/passport-google-oauth2
EDIT #1:
Here is an example with comments of the process in use with Facebook, which is the same OAuth codebase:
https://github.com/passport/express-4.x-facebook-example/blob/master/server.js

Redux can really help with achieving this and this follows the same logic as Nick B already explained...
You set up oauth on the server side and provide an endpoint that makes that call
You set up the button on you react frontend and wire that through an action to the endpoint you already setup
The endpoint supplies a token back which you can dispatch via a reducer to the central redux store.
That token can now be used to set a user to authenticated
There you have it.

Related

Facebook Authentication in a MERN stack website?

I'm building a facebook clone as part of The Odin Project curriculum and the first requirement is to let users sign up using real Facebook.
I'm using passport-facebook strategy in my backend express API for authentication. Once the login is successful I will redirect the user back to my frontend client passing a JWT token as a parameter, client-side I extract this JWT and store it in localStorage.
Below is the code that handles this redirect.
[
passport.authenticate('facebook', { session: false }),
(req, res, next) => {
const jsonifiedPayload = JSON.stringify(req.user.id);
const token = jwt.sign(jsonifiedPayload, process.env.JWT_SECRET);
return res.redirect(`/localhost:3000/login/${token}`);
}
]
Now my question is there way to determine where to redirect instead of hard-coding it in my middleware?
I've tried using 'referer' header but that fails if the redirect from facebook doesn't include the referer header.
Is there someother way to do this or do I have to stick to hard-coding the redirect location?

Auth0 does not work with Cypress for Auth0`s Application A but works with Application B

I am implementing authentication via Auth0 for Cypress.
Currently, I am using the solution from this question
// Pseudocode
cy.postRequestToAuth0(username, password, applicationClientId, applicationSecret)
.then(({ body: { access_token, expires_in, id_token, token_type } }) => {
cy.window().then((win) => {
win.localStorage.setItem(
`##auth0spajs##::${client_id}::${audience}::${scope}`,
JSON.stringifyReceivedToken()
);
});
});
For Application A (Auth0 term), It returns POST 200 for auth0/token endpoint and authorizes a user (No login button, actual user profile instead).
For Application B, which is an absolute copy of application A, it returns POST 200 from auth0/token but it does not authorize a user (Login button is visible).
The scope of token response is the same: "scope":"openid profile email offline_access".
Connections and Grant types from the setting are the same.
Logs say that authentication is successful for both applications.
Any ideas why the stored token for Application A is valid, but the token for Application B is not?

How to authenticate in asp.net core rest api from react app

I have 2 asp.net core web applications: REST API and React UI. React UI uses default Individual User Accounts option for authenticating. They are separate solutions.
What I want is to authenticate in API using this default authentication. But I'm don't know how can I do this. So what I want is
Make a call from UI -> Grab user credentials -> Go to API method -> Validate user (e.g. role) -> Return response
As far as I know, default authentication sets AspNetCore.Identity.Application cookie that used for auth in react. Probably, I can somehow parse it or just use it to authenticate on API side. I thought it's JWT token, but seems like it's not
React application sends request:
Url: /auth
Body: JSON.stringify({username: "john", password: "password123" })
REST API handles /auth request
Validate that username exists
Checks that password is correct
Either sets a cookie that the user is signed in or returns a token that the React app can save for future requests
React app sends request to get items
Url: /items
Headers, include token or set withCredentials to pass cookie
REST API handles /items request
Makes sure cookie/token is set
Validate token/cookie
Return items if everything is ok.

how to handle passport-facebook callback in angular client?

I am developing a MEAN application. I am using passport for authentication- local, facebook and google strategies.
I am using angularjs client. All the routing is handled at client. I am only consuming server data apis.
When using passport-facebook strategy, I am using below code at node server as per passport docs.
app.get('/auth/facebook',passport.authenticate('facebook-auth', { scope : ['email'] }));
app.get('/auth/facebook/callback',passport.authenticate('facebook-auth', {
successRedirect : '/home',
failureRedirect : '/login',
scope:['email']
}));
Problem I am facing is when user click on "Sign in using Facebook" button
<i class="fa fa-facebook"></i> Sign in using Facebook
Client will access "/auth/facebook" route that will eventually redirect user to facebook page for validating user's credentials.
After successful validation, user will be redirected to route "/home" as defined in "successRedirect" value.
Now the thing is, I want to use custom callback function instead of defining redirects for success or failure. It will look like below:
app.get('/auth/facebook/callback',passport.authenticate('facebook-auth', function(err,user,info){
if(err){
throw err;
}
else if(user === 'userexists'){
res.json({
'state':false,
'message':'User with this e-mail already exists'
});
}
else{
req.logIn(user,function(loginErr){
if(loginErr){
throw loginErr;
}
res.json({
'state':true,
'message':'You logged in successfully!'
});
});
}
}));
The root problem I am facing here, I can not use above custom callback as Client is not calling the "auth/facebook/callback" route, it is called by facebook.
So, there is no success handler waiting to catch above callback's response at client side!!
I want some way to get response in json form at client to eliminate server side redirection and also way to pass message and username to client after successful authentication by facebook.
I am about to give up with passport. Hoping for any possible solution before removing a lot of code!
Thanks
This can be accomplished by redirecting to another endpoint inside the facebook callback handler. There is no need to do res.json() on the callback from facebook since they only make a request to that in order to let you know if auth failed or succeeded. From their docs:
// GET /auth/facebook/callback
// Use passport.authenticate() as route middleware to authenticate the
// request. If authentication fails, the user will be redirected back to the
// login page. Otherwise, the primary route function function will be called,
// which, in this example, will redirect the user to the home page.
So facebook returns control over request process back to you when they call /auth/fb/callback but it's up to you what to do next. Since once the user is successfully authenticated, you would have req.user available throughout the whole session. At this point, you can redirect to something like the have in the example /account and check if req.user with req.isAuthenticated() and complete the flow you desire.

Authentication using Angularjs

I am fairly new to AngularJS
I have a resource that I use for user management which is part of a service following this article.
Once sending the login request to the server I am getting a response with a set-cookie as part of the header.
What is the best practice to add this cookie to every request I am sending to the server?
myApp.factory('UserService', ['$resource', function ($resource) {
var userRes = $resource('http://<MyDomain>/api/v1/user/:param',
{param: '#param'},
{
login: {
method: 'POST'
},
logout: {
method: 'DELETE'
}
});
var user;
return {
signIn: function () {
user = userRes.login({param: 'login'}, {"email": "SomeName#MyDomain.com", "password": "test1"});
userRes.get({param: '1'});
},
userRes.login has set-cookie header in on the response
userRes.get does not send the cookie that was just received.
Cheers
Since your API is in a different domain you can't use cookies in this case. We've tried and we failed to put it simple there is no way, not only it doesn't work with CORS but also it doesn't work if you embed an iframe. The iframe trick fails on safaris mostly but it is not reliable.
What we usually do is to return a JWT (Json Web Token) from the API and attach a header then to every API request as Authorization: Bearer JWT.
This JWT can be decoded using a public key from the front end (and it will contain the user profile) and validad with a private key in the backend.
JWT is simple and there are plenty of libraries for every language/technology.
Auth0 is an authentication broker that can validate with any identity provider or custom databases, and it returns JWTs using standars. It provides a clientID that can be used to decode the profile in the front end and a secret to validate the tokens in the backend as well as client side library to do this.
Disclaimer: I work for auth0.

Resources