I have a setup as such:
-backend
-model
--User.js
-routes
--auth.js
-database
--connection.js
-passport
--passport.js
--server.js
-client
-react folders / files
...
In my server.js:
const express = require("express");
const cookieSession = require("cookie-session");
const passport = require("passport");
const { authRoutes } = require("./routes/auth");
require("./model/User");
require("./services/passport");
require("./services/mongoConnect");
const app = express();
app.use(
cookieSession({
maxAge: "30 * 24 * 60 * 60 * 1000",
keys: "MY-KEY",
})
);
app.use(
cors({
origin: true,
methods: "GET, POST, PATCH, DELETE, PUT",
allowedHeaders: "Content-Type, Authorization",
})
);
app.use(passport.initialize());
app.use(passport.session());
const PORT = process.env.PORT || 5000;
app.use("/", authRoutes);
app.listen(PORT);
Inside my auth.js file inside my router
const passport = require("passport");
const router = require("express").Router();
router.get(
"/auth/google",
passport.authenticate("google", {
scope: ["profile", "email"],
})
);
router.get("/auth/google/callback", passport.authenticate("google"));
router.get("/api/logout", (req, res) => {
req.logout();
res.send(req.user);
});
router.get("/api/current_user", (req, res) => {
res.send(req.user);
});
module.exports = {
authRoutes: router,
};
Inside passport.js file I have
const passport = require("passport");
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const mongoose = require("mongoose");
const User = mongoose.model("users");
passport.use(
new GoogleStrategy(
{
clientID: "MY-ID",
clientSecret: "MY-SECRET",
callbackURL: "/auth/google/callback",
proxy: true,
},
(accessToken, refreshToken, profile, done) => {
User.findOne({ googleId: profile.id }).then((existingUser) => {
if (existingUser) {
done(null, existingUser);
} else {
new User({
googleId: profile.id,
})
.save()
.then((newUser) => {
done(null, newUser);
});
}
});
}
)
);
passport.serializeUser((user, done) => {
done(null, user._id);
});
passport.deserializeUser((id, done) => {
User.findById(id).then((user) => {
done(null, user);
});
});
My frontend consists of a button that hits my auth/google route. However it logs a cors issue error in the console.
(redirected from 'http://localhost:3000/auth/google') from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Doea anyone know how i can solve this? In mu google console I have the redirect url setup as 'http://localhost:3000/auth/google/callback'
First you need to update the cors origin with '*' or your client specific whitelisted url. But that alone is not going to solve the problem.
your button should load the auth url on the browser instead of making an api call to the auth/google route. So you can make the button a href link instead.
You have misconfigured CORS on your backend.
First, you are using wrong syntax when it comes to methods - there should be no spaces when you are listing them (I don't know how they are parsing the string but you should probably stick with the documentation -
according to documentation, you can specify methods either as a string: 'GET,PUT,POST' or a list: ['GET', 'PUT', 'POST'].
Second, you are missing OPTIONS HTTP method in the methods which is used by browsers in preflight request to test server's CORS configuration.
Third, are you sure about the specified headers?
The easiest option is to drop the CORS options configuration completely if you are not sure what you are doing and apply the cors globally before every request.
app.use(cors());
Or to only specific path where you want to allow CORS.
app.get("/somepath", cors(), (req, res) => {...
Maybe you didn't configure cors policy in your backend.
The most simple way is to install cors package if you use Express.
npm install cors
And use it as a middleware of your express app.
var app = express()
var cors = require('cors');
...
app.use(cors());
The problem is even though you have used cors package you have not added the following Access-Control-Allow-Origin option in cors. Try this :
app.use(
cors({
origin: '*',
methods: "GET, POST, PATCH, DELETE, PUT",
allowedHeaders: "Content-Type, Authorization",
})
);
Or as yash has mentioned simply use:
app.use(cors())
For more reference check the official npm cors documentation
Related
I have implemented local as well as google login using passport.js in a mern web application. The local authentication is working fine with the frontend but I am getting errors when using the Google strategy.
Error:
Access to XMLHttpRequest at 'https://accounts.google.com/o/oauth2/v2/auth?.........' (redirected from 'http://localhost:5000/auth/google') from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Error: Network Error
at createError (createError.js:16)
at XMLHttpRequest.handleError (xhr.js:84)
Issue shown by the console:
Indicate whether a cookie is intended to be set in a cross-site context by specifying its SameSite attribute.
Indicate whether to send a cookie in a cross-site request by specifying its SameSite attribute
I tested the google strategy using POSTMAN and it was working fine but when requesting from my frontend there seems to be some issue.
server.js
require("dotenv").config();
const express=require("express");
const cors=require("cors");
const mongoose=require("mongoose");
const session=require("express-session");
const passport=require("passport");
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const errorController=require("./controllers/errorController")
const app=express();
const port = process.env.PORT || 5000;
app.use(
cors({
origin: "http://localhost:3000", // <-- location of the react app were connecting to
credentials: true,
})
);
app.use(express.static("public"));
app.use(express.urlencoded({extended: true}));
app.use(express.json());
app.use(session({
secret:process.env.SECRET,
resave:false,
saveUninitialized:false,
}));
app.use(passport.initialize());
app.use(passport.session());
const User=require("./models/user.model");
passport.use(User.createStrategy());
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
passport.use(new GoogleStrategy({
clientID: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
callbackURL: "http://localhost:5000/auth/google/keeper"
},
function(accessToken, refreshToken, profile, cb) {
console.log(profile);
User.findOrCreate({ googleId: profile.id }, function (err, user) {
return cb(err, user);
});
}
));
mongoose.connect(process.env.MONGO_URI, {useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify:false, useCreateIndex:true});
const connection = mongoose.connection;
connection.once('open', () => {
console.log("MongoDB database connection established successfully");
});
const loginRouter=require("./routes/login");
const registerRouter=require("./routes/register");
const logoutRouter=require("./routes/logout");
const authRouter=require("./routes/auth");
app.use("/login", loginRouter);
app.use("/register", registerRouter);
app.use("/logout", logoutRouter);
app.use("/auth/google", authRouter);
app.use(errorController);
app.listen(port, function(){
console.log("server started on port 5000");
});
auth.js (handles the google login related routes)
const router=require("express").Router();
const passport=require("passport");
router.get("/", passport.authenticate("google", { scope: ["profile"] }));
router.get("/keeper",
passport.authenticate("google", { failureRedirect: "/login" }),
function(req, res) {
// Successful authentication, redirect home.
res.redirect("/");
});
module.exports=router;
Axios request from the frontend
function googleLogin(event){
Axios({
method: "GET",
withCredentials: true,
url: "http://localhost:5000/auth/google",
})
.then(function(res){
console.log(res);
})
.catch(function(err){
console.log(err);
})
event.preventDefault();
}
button that triggers the request
<button className="btn btn-danger" onClick={googleLogin}>Sign in with Google</button>
You can't make an axios call to the /auth/google URL!!
Here's my solution...
// step 1:
// onClick handler function of the button should use window.open instead
// of axios or fetch
const googleLogin = () => window.open("http://[server:port]/auth/google", "_self")
//step 2:
// on the server's redirect route add this successRedirect object with correct url.
// Remember! it's your clients root url!!!
router.get(
'/google/redirect',
passport.authenticate('google',{
successRedirect: "[your CLIENT root url/ example: http://localhost:3000]"
})
)
// step 3:
// create a new server route that will send back the user info when called after the authentication
// is completed. you can use a custom authenticate middleware to make sure that user has indeed
// been authenticated
router.get('/getUser',authenticated, (req, res)=> res.send(req.user))
// here is an example of a custom authenticate express middleware
const authenticated = (req,res,next)=>{
const customError = new Error('you are not logged in');
customError.statusCode = 401;
(!req.user) ? next(customError) : next()
}
// step 4:
// on your client's app.js component make the axios or fetch call to get the user from the
// route that you have just created. This bit could be done many different ways... your call.
const [user, setUser] = useState()
useEffect(() => {
axios.get('http://[server:port]/getUser',{withCredentials : true})
.then(response => response.data && setUser(response.data) )
},[])
Explanation....
step 1 will load your servers auth url on your browser and make the auth request.
step 2 then reload the client url on the browser when the authentication is
complete.
step 3 makes an api endpoint available to collect user info to update the react state
step 4 makes a call to the endpoint, fetches data and updates the users state.
Im using Passport.js with express in my app to login with Google Oauth. But when i try to sign in, i get the following error: invalid parameter value for redirect_uri: Missing authority: http:localhost:3000/google/callback from which when i access localhost:3000/google/callback, i get Missing required parameter: scope. The relevant code:
const express = require("express");
const cors = require("cors");
const mongoose = require("mongoose");
const passport = require("passport");
const app = express();
const port = process.env.PORT || 3000;
require("dotenv").config();
require("./passport-setup")
app.use(passport.initialize())
app.use(passport.session())
app.get('/success', (req, res) => {
res.render("/profile.html")
})
app.get('/login', passport.authenticate('google', { scope: 'email' }));
app.get('/google/callback', passport.authenticate('google', { failureRedirect: '/failed' }),
function(req, res) {
// Successful authentication, redirect home.
res.redirect('/success');
}
);
passport config(relevant code):
const passport = require("passport");
const GoogleStrategy = require("passport-google-oauth2").Strategy
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.CALLBACK_URL,
passReqToCallback: true
},function(request,accessToken,refreshToken,profile,done){
console.log(profile)
return done(null, profile)
}
))
PS: I found this answer but i don't know what he means by 'JSON key'. Maybe the API updated.
Any help would be really appreciated.
Thanks in advance.
EDIT
The callback url I provided to google was not matching my `app.get`. Fixed.
PS: I found this answer but i don't know what he means by 'JSON
key' - the person referred to your GoogleStrategy which is A JSON
object contains zero, one, or more key-value pairs.
/* SECURE COOKIES TRUE MADE GOOGLE SIGN IN / SIGNUP WORK */
I was getting an error when trying to Sign in/up with Google account: Missing required parameter: scope and the following lines of code added before app.use(passport.initialize()); made it work!
if (app.get("env") === "production") {
app.set("trust proxy", 1);
session.cookie.secure = true;
}
You might have to implement express-session to make this work (check the npm website for docs on how to use) but simply declare at the top:
const session = require("express-session");
before app.use(passport.initialize()) add this lines:
app.use(
session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {},
})
);
My callback URL was http:localhost:3000/google/callback
Setting it to /gooogle/callback worked for me.
I'm currently running a webserver using the MERN stack, and I'm trying to get OAuth login working properly. However, when I click the "login with google" button, react loads the homepage (but the URL changes). Fetching the URL directly gets a 302 response from the server, but my front-end doesn't change.
Server.js
const express = require('express');
const path = require('path');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const logger = require('morgan');
const cors = require('cors');
const secure = require('express-force-https');
const passport = require('passport');
const cookieSession = require('cookie-session');
require('dotenv').config();
const app = express();
const port = process.env.PORT || 5000;
const dbRoute = process.env.MONGODB_URI || 'NO DB ROUTE PROVIDED';
// db setup
mongoose.connect(
dbRoute,
{
useNewUrlParser: true,
useUnifiedTopology: true,
dbName: process.env.DATABASE_NAME,
}
);
let db = mongoose.connection;
db.once('open', () => console.log("Connected to the database"));
db.on('error', console.error.bind(console, "MongoDB connection error: "));
// middleware
app.use(cors());
app.use(bodyParser.urlencoded({ extended: false })); // body parsing
app.use(bodyParser.json());
app.use(logger("dev"));
app.use(express.static(path.join(__dirname, "client", "build"))); // for serving up the clientside code
app.use(secure); // ensure that the connection is using https
app.use(cookieSession({ // cookies!
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
keys:['vcxzkjvasddkvaosd'] // yeah i'm sure that's secure enough
}));
// models
require('./models/rule');
require('./models/affix');
require('./models/user');
// passport security
require('./config/passport');
app.use(passport.initialize());
app.use(passport.session());
// routes
app.use(require('./routes'));
// The "catchall" handler: for any request that doesn't
// match one above, send back React's index.html file.
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname+'/client/build/index.html'));
});
app.listen(port);
console.log(`Server listening on ${port}`);
Route (There are a few index files in different folders, so the full path for this route it /api/user/google)
const mongoose = require('mongoose');
const passport = require('passport');
const router = require('express').Router();
const auth = require('../auth');
const User = mongoose.model('User');
router.get('/google',
passport.authenticate('google', {
scope: ['profile', 'email']
})
);
router.get('/google/callback',
passport.authenticate('google', { failureRedirect: '/affixes'}),
(req, res) => {
res.redirect('/?token=' + req.user.token);
}
);
Passport.js
const mongoose = require('mongoose');
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
require('dotenv').config();
const User = mongoose.model('User');
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id).then((user) => {
done(null, user);
})
});
passport.use(new GoogleStrategy({
clientID: process.env.OAUTH_CLIENT_ID,
clientSecret: process.env.OAUTH_CLIENT_SECRET,
callbackURL: '/api/user/google/callback',
proxy: true
},
(accessToken, refreshToken, profile, done) => {
User.findOne({ googleId: profile.id })
.then((existingUser) => {
if (existingUser) {
done(null, existingUser);
} else {
new User({ googleId: profile.id }).save()
.then((user) => done(null, user));
}
});
}
));
Frontend login page (has a fetch button and a link button. As described above, different behavior)
import React from 'react';
import {
ComingSoon
} from '../Common';
import {
Button
} from '#material-ui/core';
const handleClick = () => {
fetch('/api/user/google')
}
export default function Login() {
return (
<>
<Button onClick={handleClick}>
Login with Google
</Button>
<button>Log in with Google</button>
</>
);
}
Update: Looks like some kind of CORS issue, although I still don't know how to fix it. Browser spits out
Access to fetch at '...' (redirected from 'http://localhost:3000/api/user/google') from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Adding the requested header gives me
Access to fetch at '...' (redirected from 'http://localhost:3000/api/user/google') from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
It turns out I was quite wrong about the nature of this issue! The problem was that my fetch requests to my OAuth endpoint were calling my frontend, not my backend because the request included text/html in its Accept header. Using the react advanced proxy setup to route to the proper URI fixed the issue.
See: https://github.com/facebook/create-react-app/issues/5103 and https://github.com/facebook/create-react-app/issues/8550
I am using passport.js google strategy for user authentication.
I am using OAUTH2.
When I launch my server and hit API through browser, it launches google signin page.
But when I hit API from react front-end, it never launches the googles signin page.
Please find below server code,
const bodyParser = require('body-parser');
const express = require('express');
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
const app = express();
const cors = require('cors');
app.use(passport.initialize());
app.use(passport.session());
app.use(cors());
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader(
"Access-Control-Allow-Methods",
"GET, POST, OPTIONS, PUT, PATCH, DELETE"
);
res.setHeader(
"Access-Control-Allow-Headers",
"X-Requested-With,content-type"
);
res.setHeader("Access-Control-Allow-Credentials", true);
next();
});
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(bodyParser.json());
passport.use('google', new GoogleStrategy({
clientID: "clientID", -- my client id
clientSecret: "clientSecret", -- my clientsecret
callbackURL: "callbackURL" -- url
},
function (accessToken, refreshToken, profile, done) {
// User.findOrCreate({ googleId: profile.id }, function (err, user) {
// console.log(err,user);
// return done(err, user);
// });
console.log(profile);
return done(err, user);
}
));
app.get('/googleauth',
passport.authenticate('google', { scope: ['https://www.googleapis.com/auth/plus.login'] }));
passport.serializeUser(function (user, done) {
done(null, user);
})
passport.deserializeUser(function (user, done) {
done(null, user);
})
app.listen(4000, () => {
console.log("REST server started on port 4000");
});
Axios call from react code,
handleJGoogleLogin(e) {
e.preventDefault();
var current = this;
axios.get('http://localhost:4000/googleauth')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
I am stuck here, looking for some help.
Thanks in advance
The call from the react front-end is a bit different for the OAuth flow. Instead of the normal back-end call using fetch or axios
make the call like this:
window.open(`http://localhost:${backend-port}/auth/google`, "_self");
This command will make a get request to the back-end server AND open that google sign in window at the same time.
Took me a lot of time to figure this out but this is the way...
I think that Passport thing only works on the server. It is probably doing a redirect or something like that.
A few things under your passport.use add in another parameter
passport.use('google', new GoogleStrategy({
clientID: "clientID", -- my client id
clientSecret: "clientSecret", -- my clientsecret
callbackURL: "callbackURL" -- url
proxy: true
},
change:
const GoogleStrategy = require('passport-google-
auth').OAuth2Strategy;
to:
const GoogleStrategy = require('passport-google-oauth20').Strategy;
add in a second scope of profile
app.get('/googleauth',
passport.authenticate('google', { scope:
['https://www.googleapis.com/auth/plus.login','profile'] }));
Can you post your react code?
I am creating a login strategy for my angular 6 web app. The user clicks the login button and it triggers an HTTP request to localhost:3000/login on my node server. I am using okta as an authenticator and using the passport oidc strategy. If I go to localhost:3000/login it performs the redirect as expected to the okta sign-in the portal. When I try it from my angular front end I get a CORS error. I have enabled Access-Control-Allow-Origin with a wildcard.
//app.js
require("dotenv").config();
const express = require("express");
const app = express();
const bodyParser = require("body-parser");
const routes = require("./routes/routes");
const mongoose = require("mongoose");
const casperRoutes = require("./routes/casper-routes");
const oktaRoutes = require("./routes/okta-routes");
const slackRoutes = require("./routes/slack-routes");
const session = require("express-session");
const passport = require("passport");
const OidcStrategy = require("passport-openidconnect").Strategy;
const oktaBaseAuth = process.env.OKTA_AUTH_URI;
mongoose
.connect(
"mongodb+srv://xxxxx",
{ useNewUrlParser: true }
)
.then(() => {
console.log("connected to database!");
})
.catch(() => {
console.log("connection failed");
});
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader(
"Access-Control-Allow-Headers",
"Origin, X-requested-With, Content-Type, Accept"
);
res.setHeader(
"Access-Control-Allow-Methods",
"GET, POST, PATCH, PUT, DELETE, OPTIONS, PUT"
);
next();
});
app.use(
session({
secret: "xxx",
resave: false,
saveUninitialized: true
})
);
app.use(passport.initialize());
app.use(passport.session());
passport.use(
"oidc",
new OidcStrategy(
{
issuer: oktaBaseAuth + "/oauth2/default",
authorizationURL: oktaBaseAuth + "/oauth2/default/v1/authorize",
tokenURL: oktaBaseAuth + "/oauth2/default/v1/token",
userInfoURL: oktaBaseAuth + "/oauth2/default/v1/userinfo",
clientID: process.env.OKTA_AUTH_CLIENT_ID,
clientSecret: process.env.OKTA_AUTH_CLIENT_SECRET,
callbackURL: 'http://localhost:3000/authorization-code/callback',
scope: "openid profile"
},
(issuer, sub, profile, accessToken, refreshToken, done) => {
return done(null, profile);
}
)
);
passport.serializeUser((user,next)=>{
next(null,user);
});
passport.deserializeUser((obj,next)=>{
next(null,obj);
});
app.use('/login', passport.authenticate('oidc'));
app.use('/authorization-code/callback',
passport.authenticate('oidc', {failureRedirect: '/error'}),
(req,res)=>{
res.redirect('/');
});
function checkLogged(req,res,next){
if (req.isAuthenticated()){
return next();
}
res.redirect('/login');
}
app.use("/api/casper",checkLogged, casperRoutes);
app.use("/api/data",checkLogged, routes);
app.use("/api/okta", checkLogged,oktaRoutes);
app.use("/api/slack", checkLogged, slackRoutes);
module.exports = app;
The front end service
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Subject, Observable } from "rxjs";
import * as OktaSignIn from "#okta/okta-signin-widget";
import { Injectable } from "#angular/core";
import { Router } from "#angular/router";
#Injectable({
providedIn: "root"
})
export class OktaService {
widget;
isAuthenticated = false;
user;
private headers = new HttpHeaders();
private authStatusListener = new Subject<boolean>();
constructor(private http: HttpClient, private router: Router) {}
loginUser(){
this.http.get('http://localhost:3000/login').subscribe(res=>console.log(res));
}
}
And the error
Failed to load https://dev-795809.oktapreview.com/oauth2/default/v1/authorize?response_type=code&client_id=0oag898j7zj7j8jqw0h7&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauthorization-code%2Fcallback&scope=openid%20openid%20profile&state=rtFMOtsHHBOc90UcM3nLIz%2Fp: Redirect from 'https://dev-795809.oktapreview.com/oauth2/default/v1/authorize?response_type=code&client_id=0oag898j7zj7j8jqw0h7&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauthorization-code%2Fcallback&scope=openid%20openid%20profile&state=rtFMOtsHHBOc90UcM3nLIz%2Fp' to 'https://dev-795809.oktapreview.com/login/login.htm?fromURI=/oauth2/v1/authorize/redirect?okta_key=c8hyLEuNV3KeYJyLfw2EU9xDjqsqeSOLft2jLzh-07E' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.
I've enabled CORS on my OKTA app as well. I read somewhere this may be because I cannot resolve CORS with a wildcard for authorization data. How can I connect to the backend for login using angular front-end running on a different port?
This happens because you're trying to make an XHR request to your backend. If you change it to redirect to your backend, it should work.
loginUser() {
window.location.href = 'http://localhost:3000/login'
}
The problem with this approach is that you'll likely be redirected back to your Node app after successfully logging in, rather than your Angular app. You could write custom code in your Node app to redirect back to the referrer after authentication. I've done this with Spring Security, but I'm not sure how to do it for Node.
Another option would be to use our Angular SDK and do the authentication in your Angular app, and set up your Node API as a resource server that simply validates the access token that's passed in.