How to logout the user from main domain and subdomain in ReactJs - reactjs

I'm building a react app where users get a subdomain on signup and when users log in or sign up on the main domain, I save their profile data to local storage and redirect them to their subdomain along with the token. I encode user profile data in this token like name, and username. When users get redirected to their subdomain, I decode the token and save the user data to the local storage for the subdomain so that I can access the logged-in user data from the subdomain. I am redirecting the user like this:
window.location.href = `http://${data?.result?.username}.localhost:3000?token=${data?.token}`;
But right now, what happens is when a user logs out from the subdomain but it doesn't log out from the main domain. I would like to know how I can log out a user from the main as well as the subdomain. I delete the user data from the local storage when a user clicks on the log out.
sign in code:
export const signin = (formData) => async (dispatch) => {
try {
// login the user
const { data } = await api.signIn(formData);
await dispatch({ type: "AUTH", data });
window.location.href = `http://${data?.result?.username}.localhost:3000?token=${data?.token}`;
} catch (error) {
dispatch({ type: "ERROR", data: error?.response?.data });
}
};
Reducer code:
const authReducer = (state = { authData: user }, action) => {
switch (action.type) {
case "AUTH":
localStorage.setItem("profile", JSON.stringify({ ...action?.data }));
return { ...state, authData: action?.data };
case "LOGOUT":
localStorage.clear();
return { ...state, authData: null };
default:
return state;
}
};
log out code:
const logout = () => {
dispatch({ type: "LOGOUT" });
history.push("/");
};

Related

When sign in with google firebase always register user as new

Tech: Firebase, Next.js, Google Sign in, Firebase Stripe exstension
Bug reproduction:
When login with Google
Subscribe on stripe
Stripe saves subscription data for that user in firestore
Logout
Login in with Google and old data are overide with new one, and Subscription is lost
Does anyone had similar problem?
Maybe my implementation of Sign-in is bad, here is the Google Sign in code:
const handleGoogleLogin = () => {
signInWithPopup(auth, googleProvider)
.then(async result => {
if (!result.user) return;
const { displayName, email, uid, providerData, photoURL, phoneNumber } =
result.user;
const name = splitName(displayName as string);
const providerId =
(providerData.length && providerData[0]?.providerId) || '';
const data = {
firstName: name?.firstName || '',
lastName: name?.lastName || '',
email,
photoURL,
phoneNumber,
providerId,
};
await updateUser(uid, data);
})
.catch(error => {
console.log('Google login error: ', error);
});
};
Update user function:
export const updateUser = async (uid: string, data: UpdateUserParams) => {
try {
if (!uid) {
return;
}
await setDoc(doc(firestore, 'users', uid), {
account: {
...data,
initials: `${data.firstName[0]}${data.lastName[0]}`,
},
});
} catch (error) {
console.error('Error updating user: ', error);
}
};
setDoc is overwriting the contents of the document with each sign-in. You should instead use set with merge to prevent overwriting the fields you don't want to lose, or check first if the document exists before creating it.
See also:
https://firebase.google.com/docs/firestore/manage-data/add-data#set_a_document
Difference Between Firestore Set with {merge: true} and Update

React JWT Authentication: is it secure to generate an access token together with the user data object?

I am implementing a JWT authentication in React and I've got this authentication context with register, login and logout:
function AuthProvider({children}) {
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
const initialize = async () => {
try {
const accessToken = localStorage.getItem('accessToken');
if (accessToken && isValidToken(accessToken)) {
setSession(accessToken);
const response = await axios.get('/api/account/my-account');
const {user} = response.data;
dispatch({
type: 'INITIALIZE',
payload: {
isAuthenticated: true,
user,
},
});
} else {
dispatch({
type: 'INITIALIZE',
payload: {
isAuthenticated: false,
user: null,
},
});
}
} catch (err) {
console.error(err);
dispatch({
type: 'INITIALIZE',
payload: {
isAuthenticated: false,
user: null,
},
});
}
};
initialize();
}, []);
const login = async (email, password) => {
const response = await axios.post('/api/account/login', {
email,
password,
});
const {accessToken, user} = response.data;
setSession(accessToken);
dispatch({
type: 'LOGIN',
payload: {
user,
},
});
};
const register = async (email, password, firstName, lastName) => {
const response = await axios.post('/api/account/register', {
email,
password,
firstName,
lastName,
});
const {accessToken, user} = response.data;
localStorage.setItem('accessToken', accessToken);
dispatch({
type: 'REGISTER',
payload: {
user,
},
});
};
const logout = async () => {
setSession(null);
dispatch({type: 'LOGOUT'});
};
return (
<AuthContext.Provider
value={{
...state,
method: 'jwt',
login,
logout,
register,
}}
>
{children}
</AuthContext.Provider>
);
}
export {AuthContext, AuthProvider};
As you can see, my API would generate an access token and would pass the user object so I can get his name and role on the next protected page.
Is this secure enough or are there better alternatives?
Thank you.
It is secure enough as long as you enforce security in the server.
Basically, whenever a user authenticates, you provide the JWT and an unencrypted payload with user information, such as identifiers like username and email as well as access rights. So far so good.
Now you are thinking: If I save this stuff in say, local storage, the user could change the stored access rights and give him/herself more rights. Well, while this could be true, it should serve for nothing because the user can only save this copy of the data, which probably controls the visibility or availability of menu items, buttons and the like according to the level of access. What should really drive the ability to perform an action is the JWT, and the user cannot alter this JWT without access to the secret or private key used to digitally sign the token.
So yes, I'd say it is secure enough. If you have a naughty user, know that said user cannot really post new data (for instance) if the access right is not digitally signed in the JWT.

How to make simple protected route using nextAuth?

I wanna make simple protected route.
I have credentials provider and nextAuth middleware. I just wanna make simple logic:
if user is logged in he can visit /profile, and if he visits /signup or /signin redirect him to /profile, and if he isnt logged he cant visit /profile and redirect him to /signin
some routes are neutral - for example he can visit /shop while being logged in or not.
there is my [...nextauth].ts
export default NextAuth({
session: {
strategy: 'jwt',
},
providers: [
CredentialsProvider({
type: 'credentials',
async authorize(credentails) {
const { password, email } = credentails as Signin
try {
const client = await connectToDatabase()
if (!client) return
const db = client.db()
const user = await existingUser(email, db)
if (!user) throw new Error('Invalid credentails!')
const isPasswordCorrect = await verifyPassword(password, user.password)
if (!isPasswordCorrect) throw new Error('Invalid credentails!')
return { email: user.email, name: user.name, id: user._id.toString() }
} catch (e: unknown) {
if (e instanceof Error) {
throw new Error(e.message)
}
}
},
}),
],
})
Apart from other answers what you can do is-
At component mount at signin and sign up check user is authenticated or not. If authenticated. use router.push to profile else be at signin/signup.
At profile again check for authentiction at component mount, if not auth push to signin else be at profile. Important thing here is don't show the layout, content of profile page before checking user is authenticated or not. Use a spiner or loader till auth check is going on.
write a middleware
const authorizedRoles = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return next(
// write logic to handle errors
new ErrorHandler(
`Role (${req.user.role}) is not allowed`,
403
)
);
}
next();
};
};
then whichever routes you want to protect, use this middleware. Then on protected pages' getServerSideProps
export async function getServerSideProps(context) {
const session = await getSession({ req: context.req });
if (!session || session.user.role !== "admin") {
return {
redirect: {
destination: "/home",
// permanent - if `true` will use the 308 status code which instructs clients/search engines to cache the redirect forever.
permanent: false,
},
};
}
return {
props: {},
};
}

Persist auth state in react/react native for Firebase

I am using react native for an ios app and firebase for authentication. Every time I leave the app and come back, it asks for a login. I want to persist the firebase login but don't really know where to put it.
I know I need to put this in:
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL)
I have the following signIn function that runs when the login button is pressed on the signInScreen:
const signIn = async () => {
setLoading(true);
try {
await firebase.signIn(email, password);
const uid = firebase.getCurrentUser().uid;
const userInfo = await firebase.getUserInfo(uid);
const emailArr = userInfo.email.split("#");
setUser({
username: emailArr[0],
email: userInfo.email,
uid,
isLoggedIn: true,
});
} catch (error) {
alert(error.message);
} finally {
isMounted.current && setLoading(false);
}
};
I have the following signIn stuff in my firebaseContext:
const Firebase = {
getCurrentUser: () => {
return firebase.auth().currentUser;
},
signIn: async (email, password) => {
return firebase.auth().signInWithEmailAndPassword(email, password);
},
getUserInfo: async (uid) => {
try {
const user = await db.collection("users").doc(uid).get();
if (user.exists) {
return user.data();
}
} catch (error) {
console.log("Error #getUserInfo", error);
}
},
logOut: async () => {
return firebase
.auth()
.signOut()
.then(() => {
return true;
})
.catch((error) => {
console.log("Error #logout", error);
});
},
};
Where do I put the persist code I listed above from the docs?
Thanks!
When do you check if someon is signed in or not?
From the code shown it looks like you check it manuelly by calling currentUser. You have to consider that the persistance of auth state is asynchronous. That means if you call currentUser on auth before the localy saved auth state is loaded you would get there null and thing that the user is not signed in.
To get the auth state Firebase recommend to use the onAuthStateChanges event listener. With that you can listen to auth state changes no matter if you logged in or the persistet auth state is loaded.
The usage is very simple:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});
That is the reson I asked where you check if someon is signed in or not. If I could see that code I could help you adopt it to use that event listener.

#msal-browser loginPopup() method only gets the bearer token

I've a React App and I am using "#azure/msal-browser": "^2.13.1" NPM package to authenticate end users using Azure AD. I've registered my app and got my client id and tenant id etc which I use to populate MSAL config details but when I call the loginPopup() method of MSAL object instance, I can briefly see the popup login window and it closes by itself. I can see a successful response as a bearer token from this call. Though this is not what I want. I would like the web app user to enter their own credentials in the popup window and use it to authenticate against the AD of my org. Code is as per below ..
import * as msal from "#azure/msal-browser";
import {LogLevel} from "#azure/msal-browser";
const AuthService = async () => {
const MSAL_CONFIG = {
auth: {
clientId: '<appclientid>',
authority: 'https://login.microsoftonline.com/<tenantid>',
redirectUri: window.location.href,
postLogoutRedirectUri: window.location.href
},
cache: {
cacheLocation: "sessionStorage",
storeAuthStateInCookie: false,
},
system: {
loggerOptions: {
loggerCallback: (level, message, containsPii) => {
if (containsPii) {
return;
}
switch (level) {
case LogLevel.Error:
console.error(message);
return;
case LogLevel.Info:
console.info(message);
return;
case LogLevel.Verbose:
console.debug(message);
return;
case LogLevel.Warning:
console.warn(message);
return;
default:
console.info(message);
}
},
},
},
};
const scopes = {scopes: ["User.ReadWrite"]}
const msalInstance = new msal.PublicClientApplication(MSAL_CONFIG);
try {
const loginResponse = await msalInstance.loginPopup(scopes);
console.log('+++ Login response : ', loginResponse)
} catch (err) {
console.log('+++ Login error : ', err)
}
}
export default AuthService;
ok, just in case anyone else having this issue, just figured out that I needed to pass in a second value prompt: "select_account"} as a part of the scope.. see below...
const scopes = {scopes: ["User.ReadWrite"],
prompt: "select_account"}
Now it opens the popup window for the user to either select an existing logged in account or user can opt for a different one by clicking 'Select a different user' option.

Resources