This is my back-end code.
import { config } from "dotenv"
import express,{Request,Response} from "express"
import mongoose, {ConnectOptions, Error} from "mongoose";
import DeckModal from './models/deck';
import cors from "cors"
import session from "express-session";
import passport from "passport";
import User from "./models/user";
import bcrypt from "bcryptjs"
import { UserDetails } from "./types/UserDetails";
import passportLocal from "passport-local"
const LocalStrategy=passportLocal.Strategy
const MongoDBStore = require('connect-mongodb-session')(session);
const GoogleStrategy =require("passport-google-oauth20")
config()
let PORT:number=5000
const app = express()
app.use(express.json())
const store = new MongoDBStore({
uri: process.env.MONGO_URI!,
collection: 'mysessions'
});
store.on('error', function(error:Error) {
console.log(error);
});
app.use(cors({ origin:"http://localhost:5173" ,credentials:true}))
app.set('trust proxy', 1)
// create session
app.use(session({
secret:process.env.SESSION_SECRET!,
resave:true,
saveUninitialized:true,
store:store,
// cookie:{
// httpOnly:true, //An HttpOnly Cookie is a tag added to a browser cookie that prevents client-side scripts from accessing data
// sameSite:"none", // If your frontend and backend is hosted on different origin then use sameSite:none in order to share cookie.
// secure:true, // it allows only https requests
// maxAge: 1000 * 60 * 60 * 24 * 7
// }
}))
app.use(passport.initialize())
app.use(passport.session())
// local strategy
passport.use(new LocalStrategy(
{usernameField:"email"},
function(username, password, done) {
User.findOne({ email: username }, function (err:Error, user:UserDetails) {
if (err) { return done(err); } // If there is error we will return an error to done function.
if (!user) { return done(null, false); } // If we don't find any user then we will return null and as there is no error we will send false
bcrypt.compare(password,user.password!,(err,result)=>{
if(err) throw err
if(result===true){
return done(null,user)
}
else{
return done(null,false)
}
})
});
}
));
//gooogle strategy
passport.use(
new GoogleStrategy(
{
callbackURL: "/auth/google/callback",
clientID: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
},
async (accessToken:any, refreshToken:any, profile:any, done:any) => {
const userInfo:UserDetails={
email:profile.emails[0].value,
profile_picture:profile.photos[0].value,
username:profile.displayName
}
try{
const user= await User.findOne({ email: profile.emails[0].value })
if(!user){
const newUser= new User(userInfo)
await newUser.save()
return done(null,newUser)
}
return done(null,user)
}
catch(err){
return done(null,false,err)
}
}
)
);
passport.serializeUser((user:any,done)=>{
return done(null,user._id)
})
passport.deserializeUser((id:string,done)=>{
User.findById(id,(err:Error,doc:any)=>{
const userInfo={
username:doc.username,
email:doc.email,
profile_picture:doc.profile_picture,
}
return done(null,userInfo)
})
})
// This is the recomended way to connect to the mongodb database using mongoose. source: https://docs.cyclic.sh/how-to/using-mongo-db#connection-example-mongoclient
mongoose.connect(process.env.MONGO_URI!,{
useUnifiedTopology: true,
useNewUrlParser: true
} as ConnectOptions )
.then(()=>{console.log("Connected to mongodb successfully");
// We will start to listen for request once the DB is connected
app.listen(process.env.PORT || PORT,()=>{
console.log("server is running on port "+PORT)
})
})
.catch((err:Error)=>{console.log(err)});
//Google auth
app.get('/auth/google', passport.authenticate('google', {
scope: ['profile','email']
// scope:['openid','profile', 'email']
}));
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: "http://localhost:5173/login", failureMessage: true,successRedirect:"http://localhost:5173/auth/success"}));
app.get("/getuser",(req,res)=>{
if(req.user){
res.json(req.user) // here we get user in req.user because passports deserialize function attaches it automatically
}
else{
res.json(null)
}
})
app.post('/auth/logout', function(req, res, next) {
req.logout(function(err) {
if (err) { return next(err); }
res.send('done');
});
});
// Local auth
app.post("/signup",async(req:Request,res:Response)=>{
const {username,email,password}=req?.body
if(!username && !password && !email){
res.send("Please provide name, email and password")
return
}
try{
const user:UserDetails | null=await User.findOne({username})
if(!user){
const hashedPassword=await bcrypt.hash(password,10)
const newUser= new User({
username,
email,
password:hashedPassword
})
await newUser.save()
res.send("success")
}
else{
res.send("User already exist")
}
}
catch(error){
console.log(error)
}
})
app.post('/login',
passport.authenticate('local'),
(req, res) => {
if(req.user){
res.redirect("http://localhost:5173/auth/success")
}
else{
res.status(200).json({success:false});
}
}
);
I tried redirecting using successRedirect but that is also giving me same error
app.post('/login',
passport.authenticate('local',{successRedirect:"http://localhost:5173/auth/success"}));
**
When I authenticate using google auth it successfully redirects me but when I use local startegy for authentication it gives me cors error.**
Error message:
Access to XMLHttpRequest at 'http://localhost:5173/' (redirected from 'http://localhost:5000/login') from origin 'http://localhost:5173' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
The front-tend request looks like this-
for local strategy:
const handleLogin = async (e:FormEvent<HTMLFormElement>) => {
e.preventDefault()
const data = { email, password };
axios.post("http://localhost:5000/login",data,{withCredentials:true})
}
for google strategy:
const handleGoogleLogin = async () => {
window.open("http://localhost:5000/auth/google","_self")
}
I have added a axios interceptor within which authProvider.getAccessToken() is called to fetch token and add to header of each request.
Here is my axiosInterceptor.js
import axios from 'axios'
import { authProvider } from '../authProvider'
export const axiosApiIntance = axios.create()
export const axiosInterceptor = axiosApiIntance.interceptors.request.use(async request => {
try {
let token = await authProvider.getAccessToken()
request.headers['Authorization'] = `Bearer ${token.accessToken}`
return request
} catch (err) {
console.log(err)
}
}, error => {
return Promise.reject(error.message)
})
Here is my authProvider.js
import { LoginType, MsalAuthProvider } from 'react-aad-msal'
// The auth provider should be a singleton. Best practice is to only have it ever instantiated once.
// Avoid creating an instance inside the component it will be recreated on each render.
// If two providers are created on the same page it will cause authentication errors.
export const authProvider = new MsalAuthProvider(
{
auth: {
authority: process.env.REACT_APP_AUTHORITY,
clientId: process.env.REACT_APP_CLIENT_ID,
postLogoutRedirectUri: process.env.REACT_APP_URL,
redirectUri: process.env.REACT_APP_URL,
validateAuthority: true,
// After being redirected to the "redirectUri" page, should user
// be redirected back to the Url where their login originated from?
navigateToLoginRequestUrl: false
},
cache: {
cacheLocation: 'sessionStorage',
storeAuthStateInCookie: true
}
},
{
scopes: ['openid', 'profile', 'user.read']
},
{
loginType: LoginType.Redirect,
// When a token is refreshed it will be done by loading a page in an iframe.
// Rather than reloading the same page, we can point to an empty html file which will prevent
// site resources from being loaded twice.
tokenRefreshUri: window.location.origin + '/auth.html'
}
)
authProvider is used in App.js
<AzureAD provider={authProvider} reduxStore={configureStore}>
....
</AzureAD>
axiosInterceptor is also included in App.js.
Please provide suggestion on what could cause the component the reload indifinitely.
I have removed the authProvider.getAccessToken() and verified, it works fine. So the reload is caused due to that.
First, I suggest you to verify the Scope, authority and clientId of your AuthProvider.
I had a similar issue in one project ans I had to add the scope to the getAccessToken() function, even if I never did that in others projects..
See below:
var authenticationParameters = {
scopes: ['openid', 'profile', 'user.read'],
};
axios.interceptors.request.use(function (config): any {
return new Promise(async (resolve: any, reject: any) => {
await authProvider.getAccessToken(authenticationParameters).then((response: any) => {
config.headers["Authorization"] = "Bearer " + response.accessToken;
config.headers["Content-Type"] = "application/json";
config.headers.Accept = "application/json";
resolve(config);
})
.catch((error: any) => {
console.log(error.message);
});
});
});
Hope it help ;)
Regards
I'm trying to use MSAL and React to login to MSGraph. I get the popup to authenticate when I call userAgentApplication.loginPopup({propt: "select_account", scopes: config.scopes})
After entering my login information, it appears that I authenticated but when I try to make a request the login popup continues to display as if I didn't authenticate already. I get no errors on the console.
I refresh the page and check localStorage and see msal.error = invalid_state_error
I'm using MSAL version v1.4.6
Here is my code
ContextualMenu.js
import { msgraph } from './actions/graphAction';
const graph = useSelector((state) => state.graph);
const userAgentApplication = new UserAgentApplication({
auth: {
clientId: config.appId,
redirectUri: config.redirectUri
},
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: true
}
});
const getUserProfile = async () => {
try {
let accessToken = await userAgentApplication.acquireTokenSilent({
scopes: config.scopes
});
if (accessToken) {
let user = await getUserDetails(accessToken);
let uu = {
displayName: user.displayName,
email: user.mail || user.userPrincipalName,
givenName: user.givenName,
surname: user.surname
}
dispatch(msgraph(true, uu, null));
}
} catch (err) {
console.log(err);
}
};
const msLogin = async () => {
try {
await userAgentApplication.loginPopup({
prompt: "select_account"
});
getUserProfile();
}
catch (err) {
console.log('failed', err);
}
};
const emailFiles = () => {
setRootClassName('close');
if (graph.isAuthenticated) {
checkSelectedFile();
return false;
}
msLogin();
}
After loginPopup is called it never gets to getUserProfile and doesn't error either.
Please any help is appreciated
I have the following code
this.setState({loading: true}, () => {
if (googleUser && googleUser.profileObj) {
app.auth().signOut();
// We need to register an Observer on Firebase Auth to make sure auth is initialized.
var unsubscribe = app.auth().onAuthStateChanged(firebaseUser => {
unsubscribe();
// Check if we are already signed-in Firebase with the correct user.
if (!this.isUserEqual(googleUser, firebaseUser)) {
// Build Firebase credential with the Google ID token.
var credential = firebase.auth.GoogleAuthProvider.credential(
googleUser.getAuthResponse().id_token);
// Sign in with credential from the Google user.
app.auth().signInWithCredential(credential).catch(error => {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
this.setState({error: errorMessage, loading: false})
}).then(() => {
app.auth().onAuthStateChanged((user) => {
if (user) {
user.getIdToken().then(idToken => {
// Session login endpoint is queried and the session cookie is set.
// CSRF protection should be taken into account.
const csrfToken = Utils.getCookie('csrfToken');
this.postIdTokenToSessionLogin('/auth/session-login', idToken, csrfToken, 'google', googleUser.profileObj.googleId);
});
} else {
this.setState({error: "There was an error", loading: false});
}
});
})
} else {
this.setState({error: "You are already signed in.", loading: false});
}
});
}
});
The code is not working, it's not setting loading to true and not re-rendering before invoking the callback function in setState.
Does anyone know what's going on?
my project is composed from a webapi and a SPA application.
I try to implement authentication using Adal.js.
this is my javascript code:
$(function () {
var endpoints = {
"https://demowebapi2017.azurewebsites.net/api/values/7": "WEB API ID"
};
window.config = {
tenant: '7dda5c2-2fb6-4f82-...',
clientId: 'CLIENT ID',
endpoints: endpoints
};
window.authContext = new AuthenticationContext(config);
$("#login").click(function () {
window.authContext.login();
});
$("#logout").click(function () {
window.authContext.logOut();
});
$("#clickMe").click(function () {
var user = window.authContext.getCachedUser();
console.log(user);
window.authContext.acquireToken('https://demowebapi2017.azurewebsites.net', function (error, token) {
console.log(error);
console.log(token);
}
);
});
});
Login works fine, I can see the login IFRAME for entering my credentials.
When I click 'clickMe' I get the error message: 'User login is required' and user is null.
Everything works fine using Angular and Adal-angular.js, so I thing all Azure configuration is fine.
Has anybody have an idea about what happen?
After you login, the app need to init the user from parsing the id_token from the hash. Here is the demo code for your reference:
$(function () {
var endpoints = {
"https://graph.windows.net": "https://graph.windows.net"
};
window.config = {
tenant: 'xxxx.onmicrosoft.com',
clientId: 'aac92cf9-32ab-4004-aeab-1046389dff79',
endpoints: endpoints
};
window.authContext = new AuthenticationContext(config);
$("#login").click(function () {
window.authContext.login();
});
$("#logout").click(function () {
window.authContext.logOut();
});
$("#clickMe").click(function () {
var user = window.authContext.getCachedUser();
console.log(user);
window.authContext.acquireToken('https://graph.windows.net', function (error, token) {
console.log(error);
console.log(token);
}
);
});
function init(){
if(window.location.hash!="")
window.authContext.handleWindowCallback(window.location.hash);
}
init();
});