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

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.

Related

Next-auth newUser redirection ignored with Credentials Provider, works with Social Providers

next-auth: "^4.15.0"
Providers in use: Credentials, Google and Facebook.
Problem: New user redirection works with socials, but not with credentials.
Why and how to fix this?
pages: {
signIn: '/auth/signin',
newUser: '/intro'
}
Using JWT strategy, a custom MongoDB adapter, also here are signIn and redirect callbacks in case anything of this is relevant:
SignIn callback:
async signIn({ user, account, profile, email, credentials }) {
const isAllowedToSignIn = !!user
if (isAllowedToSignIn) {
return true
} else {
return false
}
}
Redirect callback:
redirect({ url, baseUrl }) {
if (url.startsWith("/")) return `${baseUrl}${url}`
else if (new URL(url).origin === baseUrl) return url
return baseUrl
},

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: {},
};
}

How to logout the user from main domain and subdomain in 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("/");
};

react native Firebase onAuthStateChanged does not get current user after closing the app while the user its authenticated using social media providers

hello we have the following problem using firebase with react native and social login providers.
The login is not persistent after a successful login using the three major social login providers (google, apple, facebook). The function onAuthStateChanged does not return the user after closing the app while the user logged in using the former providers, therefore the user has to login again every time the app is opened. This problem is not present while using auth with email and password credentials when the app persist user after login and closing the app.
we are using the following technogologies and versions
react-native: 0.63.3
firebase: 8.10.1 (previously we were using 7.8.2 )
react-native-firebase/app": "^14.2.2",
"#react-native-firebase/auth": "^14.2.2"
for creating the credentials we are using the following packages:
#react-native-google-signin/google-signin": "^7.0.3"
#invertase/react-native-apple-authentication": "^2.1.5"
react-native-fbsdk-next": "^6.2.0 -> facebook login
we don't know if it's a problem with firebase because the listener function does not get the user after closing the app with a successful login.
I hope someone can helps us out.
Edit: here is example code how we are using the firebabse and the libraries to login using apple:
on App.js, we have the auth listener
auth.onAuthStateChanged(async (user) => {
try {
if (user) {
const userInfo = await getUserById(user.uid);
const userFetched = userInfo.val();
setUser(userFetched);
}
} catch (error) {
if (error.message !== 'user is null') {
console.log('ERROR: ' + error.message);
}
}
});
login the user and creating the credentials fo
const onAppleButtonPress = async() => {
if (Platform.OS === 'android'){
return onAppleButtonPressAndroid;
}
try {
const appleAuthRequestResponse = await appleAuth.performRequest({
requestedOperation: appleAuth.Operation.LOGIN,
requestedScopes: [appleAuth.Scope.EMAIL, appleAuth.Scope.FULL_NAME],
});
const {
user: newUser,
email,
nonce,
identityToken,
realUserStatus /* etc */,
} = appleAuthRequestResponse;
if (!identityToken){
Toast.show({
type: 'error',
text1:'error al ingresar con las credenciales',
});
return;
}
// loguear con firebase here
const response = await SignInWithCredentialApple(identityToken,nonce);
const usermail = response.user.email;
const userFound = await searchUserByEmail(usermail);
if (!userFound.exists()){
const newUser = {
uid: response.user.uid,
email: usermail,
role: 'user',
userName: usermail.split('#')[0],
profileFilled: false,
created: new Date(),
authMethod: 'apple',
};
setUser(newUser);
//setUser(newUser);
await addUser(newUser.uid, newUser);
} else {
setUser(Object.values(userFound.val())[0]);
}
} catch (error){
setShowError(true);
if (error.code === appleAuth.Error.CANCELED) {
console.warn('User canceled Apple Sign in.');
setError('Operacion cancelada por el usuario');
} else {
setError(`Error: ${error.toString()}`);
}
}
};
login and creating the credentials
import authRNFirebase from '#react-native-firebase/auth';
export const SignInWithCredentialApple = async (identityToken, nounce) => {
//return auth.signInWithCredential(credential);
const credential = authRNFirebase.AppleAuthProvider.credential(
identityToken,
nounce,
);
return authRNFirebase().signInWithCredential(credential);
};

#azure/msal-browser untrusted_authority error

I've been trying to follow this tutorial...
Sign In Users From A React SPA
but I cannot get it to work. I have a personal azure account and have created an SPA application within Azure Active Directory to get a client id.
From everything I've read it says I should use https://login.microsoftonline.com/{tenant-id-here} as my authority but when I do I get the error...
ClientConfigurationError: untrusted_authority: The provided authority is not a trusted authority
I have tried adding a knownAuthorities parameter to the config, although I don't think I should have to as I'm just concerned with a single tenant.
When I do add the knownAuthorities param, the error changes to...
ClientAuthError: openid_config_error: Could not retrieve endpoints.
My config file looks like this
export const msalConfig = {
auth: {
clientId: '{client id from Azure AD Application}',
authority: 'https://login.microsoftonline.com/{tenant-id}',
redirectUri: 'http://localhost:3000',
},
cache: {
cacheLocation: "sessionStorage",
storeAuthStateInCookie: false
}
}
The sign in button that causes the error looks like this...
function handleLogin(instance) {
instance.loginPopup(loginRequest).catch(e => {
console.error(e);
})
}
function SignInButton() {
const {instance} = useMsal();
return (
<Button variant="secondary" className="ml-auto" onClick={() => handleLogin(instance)}>
Sign in
</Button>
)
}
Might I be missing something in the azure settings? Or something else in the react application itself?
UPDATE: 16/02/22
Well I've now got it working. I accidentally had the sign in button rendered inside an <a> tag, which must have been stopping the Microsoft login popup from loading. Probably trying to redirect somewhere, which prevented the MSAL process from finishing. Wasn't the most helpful error message to go on.
So to confirm, for a single tenant solution, you only need clientId and authority. And authority is definitely https://login.microsoftonline.com/{your-tenant-id}
I took a look on Github and the settings are a bit different. Try using "https://login.microsoftonline.com/common" as the authority:
const msalConfig = {
auth: {
clientId: "enter_client_id_here",
authority: "https://login.microsoftonline.com/common",
knownAuthorities: [],
cloudDiscoveryMetadata: "",
redirectUri: "enter_redirect_uri_here",
postLogoutRedirectUri: "enter_postlogout_uri_here",
navigateToLoginRequestUrl: true,
clientCapabilities: ["CP1"]
},
cache: {
cacheLocation: "sessionStorage",
storeAuthStateInCookie: false,
secureCookies: false
},
system: {
loggerOptions: {
loggerCallback: (level: LogLevel, message: string, containsPii: boolean): void => {
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;
}
},
piiLoggingEnabled: false
},
windowHashTimeout: 60000,
iframeHashTimeout: 6000,
loadFrameTimeout: 0,
asyncPopups: false
};
}
const msalInstance = new PublicClientApplication(msalConfig);
source: https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/configuration.md
Kindly add the knownAuthorities, and it's worked for my sample
const msalConfig = {
auth: {
clientId: 'enter_client_id_here',
// comment out if you use a multi-tenant AAD app
authority: 'https://login.microsoftonline.com/{tenant-id}',
knownAuthorities: ["login.microsoftonline.com"],
redirectUri: 'http://localhost:8080'
}
};

Resources