I'm getting the error below. My problem is NOT with the actual error but the fact that it is saying that the error was Uncaught. If you take a look at my auth.service.ts and sign-in.component.ts files I am catching the error.
My question is, why am getting the Error: Uncaught (in promise) error in the console? What am I missing?
I'm using
"#angular/fire": "^7.0.4"
"firebase": "^9.0.2"
"rxjs": "6.6.7"
auth.service.ts
/**
* Sign in
*
* #param credentials
*/
signIn(credentials: { email: string; password: string }): Promise<any>
{
return this.auth.signInWithEmailAndPassword(credentials.email, credentials.password)
.then((userCredential) => {
// Signed in
const user = userCredential.user;
//console.log(user);
// Store the access token in the local storage
userCredential.user.getIdToken().then(token => {
this.accessToken = token;
//console.log(token);
})
// Set the authenticated flag to true
this._authenticated = true;
// Store the user on the user service
//this._userService.user = user;
// ...
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
console.log('Show Error', error.code);
throw errorCode;
});
}
sign-in.component.ts
/**
* Sign in
*/
signIn(): void
{
// Return if the form is invalid
if ( this.signInForm.invalid )
{
return;
}
// Disable the form
this.signInForm.disable();
// Hide the alert
this.showAlert = false;
// Sign in
this._authService.signIn(this.signInForm.value)
.then(
() => {
// Set the redirect url.
// The '/signed-in-redirect' is a dummy url to catch the request and redirect the user
// to the correct page after a successful sign in. This way, that url can be set via
// routing file and we don't have to touch here.
const redirectURL = this._activatedRoute.snapshot.queryParamMap.get('redirectURL') || '/signed-in-redirect';
// Navigate to the redirect url
this._router.navigateByUrl(redirectURL);
},
(response) => {
console.log('error from auth.service', response);
// Re-enable the form
this.signInForm.enable();
// Reset the form
this.signInNgForm.resetForm();
// Set the alert
this.alert = {
type : 'error',
message: 'Wrong email or password'
};
// Show the alert
this.showAlert = true;
}
);
}
First of all, my native language is not English, so if I write like a fool you know why.
try this:
_authService.service.ts
import { getAuth, signInWithEmailAndPassword, Auth, inMemoryPersistence, browserLocalPersistence } from '#angular/fire/auth';
constructor(private _fireAuth: Auth,) {
/**
* Sign-in
*
* #param credentials
* #param rememberMe
*/
async signIn(credentials: { email: string; password: string }, rememberMe: boolean): Promise<any> {
// firebase Persistence.LOCAL browserLocalPersistence
// firebase Persistence.SESSION browserSessionPersistence
// firebase Persistence.NONE inMemoryPersistence
return new Promise(async (resolve, reject) => {
//Initialize auth()
const auth = getAuth();
// Extra function
if (rememberMe) {
await getAuth().setPersistence(browserLocalPersistence).catch(error => reject(-1));
} else {
await getAuth().setPersistence(inMemoryPersistence).catch(error => reject(-1));
}
signInWithEmailAndPassword(auth, credentials.email, credentials.password).then(async (userCredential) => {
// Signed in
const user = userCredential.user;
console.log(user);
// Store the access token in the local storage
await userCredential.user.getIdTokenResult().then(token => {
this.accessToken = token.token;
console.log(token);
})
// Set the authenticated flag to true
this._authenticated = true;
}).catch(error => reject(error.code));
});
}
Note: As you can see I have added some extra functions that you can remove if you are not interested (setPersistence), this allows you to take into account the user's choice to stay logged in if he wants to, or to remove his login when he closes the tab.
sign-in.component.ts
alert = {
userNotFound : false,
wrongPassword: false,
unknownError : false,
};
/**
* Sign in
*/
signIn(): void
{
// Return if the form is invalid
if ( this.signInForm.invalid )
{
return;
}
// Disable the form
this.signInForm.disable();
// Hide the alert
this.showAlert = false;
// Sign in
this._authService.signIn(this.signInForm.value)
.then(
() => {
// Set the redirect url.
// The '/signed-in-redirect' is a dummy url to catch the request and redirect the user
// to the correct page after a successful sign in. This way, that url can be set via
// routing file and we don't have to touch here.
const redirectURL = this._activatedRoute.snapshot.queryParamMap.get('redirectURL') || '/signed-in-redirect';
// Navigate to the redirect url
this._router.navigateByUrl(redirectURL);
},
(response) => {
console.log('error from auth.service', response);
// Re-enable the form
this.signInForm.enable();
// Reset the form
this.signInNgForm.resetForm();
// Set the alert
if (error === - 1) {
this.alert.unknownError = true;
} else if (error === 'auth/email-not-found' || error === 'auth/user-not-found') {
this.alert.userNotFound = true;
} else if (error === 'auth/wrong-password') {
this.alert.wrongPassword = true;
}
}
);
}
For me, explicitly catching the error as in the above answers still resulted in the error being sent to the console as Uncaught (in Promise). For whatever reason, in addition to sending the error to be caught via Promise.catch, Angular was routing the error through its default ErrorHandler and claiming that the error was uncaught. I had to override the default ErrorHandler with my own, detect that these were FirebaseErrors and then ignore them (since I already had an explicit Promise.catch defined where I needed it).
Some tips in case they are helpful:
Angular only recognizes error handlers defined on the root module. Definitions on child modules seem to be ignored. You get one global error handler that needs to do everything.
The core FirebaseErrors seem to be stored in a rejection property on the main error object. You can detect them like so:
import { ErrorHandler, Injectable } from "#angular/core";
import { FirebaseError } from "firebase/app";
interface AngularFireError extends Error {
rejection: FirebaseError;
}
function errorIsAngularFireError(err: any): err is AngularFireError {
return err.rejection && err.rejection.name === 'FirebaseError';
}
// Not providedIn 'root': needs special handling in app.module to override default error handler.
#Injectable()
export class YourErrorHandler implements ErrorHandler {
handleError(error: any) {
// AngularFire errors should be catchable and handled in components; no need to further process them.
if (!errorIsAngularFireError(error)) {
console.error(error);
}
}
}
And in your root module:
providers: [
...,
{ provide: ErrorHandler, useClass: YourErrorHandler }
],
as Frank mentionned you are throwing an error without catching it back at a higher lever what I would try would be to do as so:
try {
this._authService.signIn(this.signInForm.value)
.then(
() => {
// Set the redirect url.
// The '/signed-in-redirect' is a dummy url to catch the request and redirect the user
// to the correct page after a successful sign in. This way, that url can be set via
// routing file and we don't have to touch here.
const redirectURL = this._activatedRoute.snapshot.queryParamMap.get('redirectURL') || '/signed-in-redirect';
// Navigate to the redirect url
this._router.navigateByUrl(redirectURL);
},
(response) => {
console.log('error from auth.service', response);
// Re-enable the form
this.signInForm.enable();
// Reset the form
this.signInNgForm.resetForm();
// Set the alert
this.alert = {
type : 'error',
message: 'Wrong email or password'
};
// Show the alert
this.showAlert = true;
}
);
} catch(e) {}
just surround your code with a try catch block so the error is muted. I didn't tested it but maybe calling another catch method after then could do the trick still it would be at the same level (Promise level) so I'm not sure it would work.
Related
I'm trying to use Firebase custom claims to protect content for my users, but the first time a user signs up and is redirected to /protectedpage, they cannot view the page because their claim is not set. If they log out and log back in, everything works properly.
Signup Flow
User signs up with email and password
A user document is created in a users collection in Firestore
The user is redirected to /protectedpage
Creation of the user document triggers a cloud function which assigns the custom claim role=A or role=B depending on the information in the user document.
In Javascript (React), it looks like this
Client side
// Create a new user with email and password
createUserWithEmailAndPassword(auth, formValues.email, formValues.password)
.then((userCredential) => {
// Signed in
const user = userCredential.user;
// Add a new document in collection "users"
setDoc(doc(db, "users", user.uid), {
account_type: formValues.account_type,
full_name: formValues.full_name,
});
// Send email verification
sendEmailVerification(userCredential.user)
.then(() => {
// Redirect to home page
router.push('/protectedpage');
})
.catch((error) => {
console.log("Error sending email verification", error.message);
});
})
.catch((error) => {
setFormError(error.message);
})
Server side
const functions = require('firebase-functions')
const { initializeApp } = require('firebase-admin/app');
const { getAuth } = require('firebase-admin/auth');
initializeApp();
// This function runs when a document is created in
// the users collection
exports.createUser = functions.firestore
.document('users/{userId}')
.onCreate(async (snap, context) => {
// Get an object representing the document
const doc = snap.data()
const userId = context.params.userId;
// Declare customClaims
let customClaims = {};
// Assign user role
if (doc.account_type == 'A') {
customClaims["role"] = "A"
} else if (doc.account_type == 'B') {
customClaims["role"] = "B"
} else {
functions.logger.info('A role could not be assigned to user:', doc)
response.send('Error: A role could not be assigned')
}
try {
// Set custom user claims on this newly created user.
await getAuth().setCustomUserClaims(userId, customClaims);
} catch (error) {
functions.logger.info(error);
}
return "OK"
})
By the time the user gets to /protectedpage, his JWT does not have the custom claim.
Authorization
My authorization code is using a React context manager, and looks like this
import { createContext, useContext, useEffect, useState } from 'react'
import { onAuthStateChanged, signOut as authSignOut } from 'firebase/auth'
import { auth } from './firebase'
export default function useFirebaseAuth() {
const [authUser, setAuthUser] = useState(null)
const [isLoading, setIsLoading] = useState(true)
const clear = () => {
setAuthUser(null)
setIsLoading(false)
}
const authStateChanged = async (user) => {
setIsLoading(true)
if (!user) {
clear()
return
}
// Use getIdTokenResult() to fetch the custom claims
user.getIdTokenResult()
.then((idTokenResult) => {
console.log("idTokenResult", idTokenResult)
setAuthUser({
uid: user.uid,
email: user.email,
role: idTokenResult.claims.role,
})
setIsLoading(false)
})
.catch((error) => {
console.log(error)
})
}
const signOut = () => authSignOut(auth).then(clear)
// Listen for Firebase Auth state change
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, authStateChanged)
return () => unsubscribe()
}, [])
return {
authUser,
isLoading,
signOut,
}
}
const AuthUserContext = createContext({
authUser: null,
isLoading: true,
signOut: async () => {},
})
export function AuthUserProvider({ children }) {
const auth = useFirebaseAuth()
return (
<AuthUserContext.Provider value={auth}>{children}</AuthUserContext.Provider>
)
}
export const useAuth = () => useContext(AuthUserContext)
If I change user.getIdTokenResult() to user.getIdTokenResult(true), the user no longer has to sign out and sign back in to access the custom claim BUT
They need to manually refresh the page to acquire the custom claim
I think this is bad, as it's going to forcibly refresh the token on every page load ??
The Firebase docs seem to address this problem with some trickery involving "metadataRef" but I don't understand it exactly, as I think it's related to the Realtime database whereas I'm using Firestore.
Finally got this to work. Two things were tripping me up.
router.push('/protectedpage') doesn't do a hard refresh. I changed this to window.location.replace('/protectedpage')
Instead of assigning the custom claim on creation of the user record, I wrote a cloud function to do it. After my user is created, I call this function. After I get the response, then I redirect the user to /protectedpage
My cloud function looks like this
const functions = require('firebase-functions')
const { initializeApp } = require('firebase-admin/app');
const { getAuth } = require('firebase-admin/auth');
initializeApp();
// IMPORTANT:
// Note the distinction between onCall and onRequest
// With onCall, authentication / user information is automatically added to the request.
// https://stackoverflow.com/questions/51066434/firebase-cloud-functions-difference-between-onrequest-and-oncall
// https://firebase.google.com/docs/functions/callable
// Function to set a user's role as either "A" or "B"
exports.setRole = functions.https.onCall((data, context) => {
// Check that the user is authenticated.
if (!context.auth) {
// Throw an HttpsError so that the client gets the error details.
// List of error codes: https://firebase.google.com/docs/reference/node/firebase.functions#functionserrorcode
throw new functions.https.HttpsError(
'failed-precondition',
'The function must be called while authenticated.'
);
}
// Confirm that the function contains a role
if (!data.hasOwnProperty("role")) {
throw new functions.https.HttpsError(
'failed-precondition',
"The function data must contain a 'role'"
);
}
// Confirm that role is either A or B
if (data.role !== "A" && data.role !== "B") {
throw new functions.https.HttpsError(
'failed-precondition',
"'role' must be set to either 'A' or 'B'"
);
}
// Confirm that the user doesn't already have a role
if (context.auth.token.role) {
throw new functions.https.HttpsError(
'failed-precondition',
"The user's role has already been set"
);
}
// Assign the role
// IMPORTANT:
// We need to return the promise! The promise returns the response. This way, on the client,
// we can wait for the promise to get resolved before moving onto the next step.
return getAuth().setCustomUserClaims(context.auth.uid, { role: data.role })
.then(() => {
return "OK"
})
.catch((error) => {
throw new functions.https.HttpsError(
'internal',
'Error setting custom user claim'
);
})
})
and I call it from the client like this
// Handle form submission
const onSubmit = (formValues) => {
// Create a new user with email and password
createUserWithEmailAndPassword(auth, formValues.email, formValues.password)
.then((userCredential) => {
// Signed in
const user = userCredential.user;
// Send email verification
sendEmailVerification(user);
// Add a new document in collection "users"
const promise1 = setDoc(doc(db, "users", user.uid), {
account_type: formValues.account_type,
full_name: formValues.full_name,
});
// Set the user role (custom claim)
// Then force refresh the user token (JWT)
const setRole = httpsCallable(functions, 'setRole');
const promise2 = setRole({ role: formValues.account_type })
.then(() => user.getIdTokenResult(true));
// When the user document has been created and the role has been set,
// redirect the user
// IMPORTANT: router.push() doesn't work for this!
Promise.all([promise1, promise2]).then((values) => {
window.location.replace('/protectedpage');
})
})
.catch((error) => {
setFormError(error.message);
})
}
I am writing an HTTPHandler for lambda execution.
What I want is whenever lambda calls handler, it should go through prechecks and post-checks.
I want to make HTTPHandler dynamic depending on the user's type on body input. It should perform the desired action.
It is difficult to explain, please check the code snippet below and find TODOs for better explanation.
//main lambda handler
export const handler = handleHttpRequest(async (body: Login) => {
const { email, password } = body;
if (!email || !password) throw new Error("Invalid Request. Either email or password is not provided");
// const session = await login(email, password);
// if (!session) throw new UnauthorisedError();
return { json: "session" };
});
//Login.ts
import { IsEmail, IsStrongPassword } from "class-validator";
export class Login {
#IsEmail()
email: string;
#IsStrongPassword()
password: string;
}
//requestHandler.ts
import { validate } from "class-validator";
import { isJson } from "./common";
export const handleHttpRequest = (callback) => {
// TODO: Here I want to take arguments of callback function defined with their Type.
// In above example, we are passing body with type "Login". I want to get that Login as Type.
type BodyType = Parameters<typeof callback>[0]; // Not sure if this is correct.
const actualHandler = async ({ body }) => {
//TODO: As Body type was a class originally. I want to validate the inputs before execution of callback.
// I want to validate based on type user has assigned to body param in line 2.
let error;
//validate user input
if (!isJson(body)) error = new Error("Invalid user input");
body = JSON.parse(body);
if (!error) {
const validationResp = await validate(body);
if (validationResp.length > 0) error = validationResp;
}
let resp = null;
if (!error) resp = await callback(body).catch((e) => e);
else resp = error;
}
}
Please let me know If you need any further clarification on this. I think the solution will be somewhere connected to class-transformer package.
I have followed the code from the official docs. When you scroll down to the full code, it has two things that make problems. First of all, this seems weird:
const recaptchaVerifier = new RecaptchaVerifier('recaptcha-container-id', undefined, auth);
const auth = getAuth();
They define auth after using it for recaptchaVerifier. But that seems like a typo, so I just switched these two lines.
But I cannot resolve the second issue. Their code is in JavaScript, my code is in TypeScript. They use undefined as an argument in the definition of recaptchaVerifier:
const recaptchaVerifier = new RecaptchaVerifier('recaptcha-container-id', undefined, auth);
The second argument of the constructor is undefined. Since TypeScript does not allow that, I tried many things, for example these:
const undef: any = undefined; const recaptchaVerifier = new RecaptchaVerifier('recaptcha-container-id', undef, auth);
const recaptchaVerifier = new RecaptchaVerifier('recaptcha-container-id', { size: 'invisible' }, auth);
But it ALWAYS gives this error in the console:
ERROR FirebaseError: Firebase: Error (auth/argument-error).
at createErrorInternal (index-0bb4da3b.js:474:41)
at _assert (index-0bb4da3b.js:480:15)
at new RecaptchaVerifier (index-0bb4da3b.js:7369:9)
I could not find anything that helped me fix this error in the internet.
Here is my full code:
LogIn(email: string, password: string) {
const auth = getAuth();
const undef: any = undefined;
const recaptchaVerifier = new RecaptchaVerifier(
'recaptcha-container-id',
undef,
auth
);
/* It never reaches this code below here since new RecaptchaVerifier() always throws an error */
return signInWithEmailAndPassword(auth, email, password)
.then((result) => {
this.afAuth.authState.subscribe((user) => {
if (user) {
this.router.navigate(['home']);
}
});
})
.catch((error) => {
if (error.code == 'auth/multi-factor-auth-required') {
// The user is a multi-factor user. Second factor challenge is required.
const auth = getAuth();
let resolver = getMultiFactorResolver(auth, error);
const phoneInfoOptions = {
multiFactorHint: resolver.hints[0],
session: resolver.session
};
// Send SMS verification code.
const phoneAuthProvider = new PhoneAuthProvider(auth);
phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
.then((verificationId) => {
// verificationId will be needed for sign-in completion.
// Ask user for the SMS verification code via prompt (yeah, very bad UI)
const verificationCode = prompt("Enter the verification code we sent to your number");
if (verificationCode !== null) {
const cred = PhoneAuthProvider.credential(verificationId, verificationCode);
const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);
// Complete sign-in.
return resolver.resolveSignIn(multiFactorAssertion);
} else {
this.toast.error("Entered wrong code");
return null;
}
})
.then((userCredential) => {
// User successfully signed in with the second factor phone number.
this.toast.success("Code is correct. Logged in");
this.afAuth.authState.subscribe((user) => {
if (user) {
this.router.navigate(['home']);
}
});
})
.catch((error) => {
console.log(error);
// failed
this.toast.error(error.message);
});
} else if (error.code == 'auth/wrong-password') {
this.toast.error(error.message);
}
});
}
I am using Angular and angularfire. The code above is not called directly from a component, but from a service. That service though is called from my LoginComponent.
Edit. My imports are:
import { Injectable, NgZone } from '#angular/core';
import { AngularFireAuth } from '#angular/fire/compat/auth';
import {
AngularFirestore,
} from '#angular/fire/compat/firestore';
import { Router } from '#angular/router';
import { child, get, getDatabase, ref, set } from "firebase/database";
import { HotToastService } from '#ngneat/hot-toast';
import firebase from "firebase/compat/app";
import { getAuth, getMultiFactorResolver, GoogleAuthProvider, PhoneAuthProvider, PhoneMultiFactorGenerator, RecaptchaVerifier, signInWithEmailAndPassword, signInWithPopup } from 'firebase/auth';
As we talked in the comments for this to work you need an empty div with the passed id, like:
<div id="recaptcha-container-id"></div>
I am building SPA with react and I run into a problem with code directly from Azure Portal - Quickstart for Javascript. (you can download full code there)
If I create-react-app and I use the code it works just fine and I can authenticate, get token and use it in the post request.
But If I use the SAME code in my app (which is already styled and with all the functionality I need) it gives me Multiple authorities found in the cache. Pass authority in the API overload.|multiple_matching_tokens_detected` error when I authenticate.
Just to clarify authentication goes through and I see I am authenticated, just this error is bugging me and I have no idea how to debug it.
function signIn() {
myMSALObj.loginPopup(applicationConfig.graphScopes).then(function (idToken) {
//Login Success
console.log(idToken); //note that I can get here!
showWelcomeMessage();
acquireTokenPopupAndCallMSGraph();
}, function (error) {
console.log(error);
});
}
function acquireTokenPopupAndCallMSGraph() {
//Call acquireTokenSilent (iframe) to obtain a token for Microsoft Graph
myMSALObj.acquireTokenSilent(applicationConfig.graphScopes).then(function (accessToken) {
callMSGraph(applicationConfig.graphEndpoint, accessToken, graphAPICallback);
}, function (error) {
console.log(error); //this is where error comes from
// Call acquireTokenPopup (popup window) in case of acquireTokenSilent failure due to consent or interaction required ONLY
if (error.indexOf("consent_required") !== -1 || error.indexOf("interaction_required") !== -1 || error.indexOf("login_required") !== -1) {
myMSALObj.acquireTokenPopup(applicationConfig.graphScopes).then(function (accessToken) {
callMSGraph(applicationConfig.graphEndpoint, accessToken, graphAPICallback);
}, function (error) {
console.log(error);
});
}
});
}
The main thing I don`t understand is that the same code works just fine in the fresh create-react-app project, but as I use it in an already existing project (just without authentication) it breaks with mentioned error.
Full code
import React, { Component } from 'react'
import * as Msal from 'msal'
export class test extends Component {
render() {
var applicationConfig = {
clientID: '30998aad-bc60-41d4-a602-7d4c14d95624', //This is your client ID
authority: "https://login.microsoftonline.com/35ca21eb-2f85-4b43-b1e7-6a9f5a6c0ff6", //Default authority is https://login.microsoftonline.com/common
graphScopes: ["30998aad-bc60-41d4-a602-7d4c14d95624/user_impersonation"],
graphEndpoint: "https://visblueiotfunctionapptest.azurewebsites.net/api/GetDeviceList"
};
var myMSALObj = new Msal.UserAgentApplication(applicationConfig.clientID, applicationConfig.authority, acquireTokenRedirectCallBack,
{storeAuthStateInCookie: true, cacheLocation: "localStorage"});
function signIn() {
myMSALObj.loginPopup(applicationConfig.graphScopes).then(function (idToken) {
//Login Success
console.log(idToken);
showWelcomeMessage();
acquireTokenPopupAndCallMSGraph();
}, function (error) {
console.log(error);
});
}
function signOut() {
myMSALObj.logout();
}
function acquireTokenPopupAndCallMSGraph() {
//Call acquireTokenSilent (iframe) to obtain a token for Microsoft Graph
myMSALObj.acquireTokenSilent(applicationConfig.graphScopes).then(function (accessToken) {
callMSGraph(applicationConfig.graphEndpoint, accessToken, graphAPICallback);
}, function (error) {
console.log(error);
// Call acquireTokenPopup (popup window) in case of acquireTokenSilent failure due to consent or interaction required ONLY
if (error.indexOf("consent_required") !== -1 || error.indexOf("interaction_required") !== -1 || error.indexOf("login_required") !== -1) {
myMSALObj.acquireTokenPopup(applicationConfig.graphScopes).then(function (accessToken) {
callMSGraph(applicationConfig.graphEndpoint, accessToken, graphAPICallback);
}, function (error) {
console.log(error);
});
}
});
}
function callMSGraph(theUrl, accessToken, callback) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200)
callback(JSON.parse(this.responseText));
console.log(this.response);
}
xmlHttp.open("POST", theUrl, true); // true for asynchronous
xmlHttp.setRequestHeader('Authorization', 'Bearer ' + accessToken);
var dataJSON = JSON.stringify({ userEmail: null, FromDataUTC: "2012-04-23T18:25:43.511Z" })
xmlHttp.send(dataJSON);
}
function graphAPICallback(data) {
//Display user data on DOM
// var divWelcome = document.getElementById('WelcomeMessage');
// divWelcome.innerHTML += " to Microsoft Graph API!!";
// document.getElementById("json").innerHTML = JSON.stringify(data, null, 2);
}
function showWelcomeMessage() {
console.log("You are looged: " + myMSALObj.getUser().name);
// var divWelcome = document.getElementById('WelcomeMessage');
// divWelcome.innerHTML += 'Welcome ' + myMSALObj.getUser().name;
// var loginbutton = document.getElementById('SignIn');
// loginbutton.innerHTML = 'Sign Out';
// loginbutton.setAttribute('onclick', 'signOut();');
}
// This function can be removed if you do not need to support IE
function acquireTokenRedirectAndCallMSGraph() {
//Call acquireTokenSilent (iframe) to obtain a token for Microsoft Graph
myMSALObj.acquireTokenSilent(applicationConfig.graphScopes).then(function (accessToken) {
callMSGraph(applicationConfig.graphEndpoint, accessToken, graphAPICallback);
}, function (error) {
console.log(error);
//Call acquireTokenRedirect in case of acquireToken Failure
if (error.indexOf("consent_required") !== -1 || error.indexOf("interaction_required") !== -1 || error.indexOf("login_required") !== -1) {
myMSALObj.acquireTokenRedirect(applicationConfig.graphScopes);
}
});
}
function acquireTokenRedirectCallBack(errorDesc, token, error, tokenType)
{
if(tokenType === "access_token")
{
callMSGraph(applicationConfig.graphEndpoint, token, graphAPICallback);
} else {
console.log("token type is:"+tokenType);
}
}
// Browser check variables
var ua = window.navigator.userAgent;
var msie = ua.indexOf('MSIE ');
var msie11 = ua.indexOf('Trident/');
var msedge = ua.indexOf('Edge/');
var isIE = msie > 0 || msie11 > 0;
var isEdge = msedge > 0;
//If you support IE, our recommendation is that you sign-in using Redirect APIs
//If you as a developer are testing using Edge InPrivate mode, please add "isEdge" to the if check
if (!isIE) {
if (myMSALObj.getUser()) {// avoid duplicate code execution on page load in case of iframe and popup window.
showWelcomeMessage();
acquireTokenPopupAndCallMSGraph();
}
}
else {
document.getElementById("SignIn").onclick = function () {
myMSALObj.loginRedirect(applicationConfig.graphScopes);
};
if (myMSALObj.getUser() && !myMSALObj.isCallback(window.location.hash)) {// avoid duplicate code execution on page load in case of iframe and popup window.
showWelcomeMessage();
acquireTokenRedirectAndCallMSGraph();
}
}
return (
<div>
<h2>Please log in from VisBlue app</h2>
<button id="SignIn" onClick={signIn}>Sign In</button>
<button id="SignOut" onClick={signOut}>Sign Out</button>
<h4 id="WelcomeMessage"></h4>
<br/><br/>
<pre id="json"></pre>
</div>
)
}
}
export default test
it gives me Multiple authorities found in the cache. Pass authority in
the API overload.|multiple_matching_tokens_detected` error when I
authenticate
This error is caused because the auth SDK finds multiple matching tokens in cache for the input given to acquireTokenSilent.
Try adding the authority, and user if necessary:
myMSALObj
.acquireTokenSilent(
applicationConfig.graphScopes,
applicationConfig.authority
)
.then(
...
Just to get back to it. I solve it by moving the whole project into fresh create-react-app. It looks like there was more than 1 instance of MSAL object thus more than one call/token at the same time.
Weird but solved my problem.
I know this is an old question, but I'll answer it anyway since I had the same issue.
My workaround for this problem was to just clear the cache whenever this error happened. It worked in my case since this wan't a regularly occurring error for my use case.
In my project's configuration, the site is also set up to refresh and retry when an error like this occurs. So after clearing the cache, the site would reload and would work as expected since there would be no conflicting tokens in the cache.
import { AuthCache } from 'msal/lib-commonjs/cache/AuthCache';
...
const authProvider = new MsalAuthProvider(
configuration,
authenticationParameters,
msalProviderConfig,
);
authProvider.registerErrorHandler((authError: AuthError | null) => {
if (!authError) {
return;
}
console.error('Error initializing authProvider', authError);
// This shouldn't happen frequently. The issue I'm fixing is that when upgrading from 1.3.0
// to 1.4.3, it seems that the new version creates and stores a new set of auth credentials.
// This results in the "multiple_matching_tokens" error.
if (authError.errorCode === 'multiple_matching_tokens') {
const authCache = new AuthCache(
configuration.auth.clientId,
configuration.cache.cacheLocation,
configuration.cache.storeAuthStateInCookie,
);
authCache.clear();
console.log(
'Auth cache was cleared due to incompatible access tokens existing in the cache.',
);
}
I am developing an app in React Native and I want to implement logging in with Facebook.
I have an API in Node.js where I handle the logic for users to log in, etc.
I use passport.js to let users log in with either Facebook or traditional Email.
I am opening an URL in my API with SafariView which is just a regular "WebView" directly in my app.
I have tried using the following code:
class FacebookButton extends Component {
componentDidMount() {
// Add event listener to handle OAuthLogin:// URLs
Linking.addEventListener('url', this.handleOpenURL);
// Launched from an external URL
Linking.getInitialURL().then((url) => {
if (url) {
this.handleOpenURL({ url });
}
});
}
componentWillUnmount() {
Linking.removeEventListener('url', this.handleOpenURL);
}
handleOpenURL({ url }) {
// Extract stringified user string out of the URL
const [, user_string] = url.match(/user=([^#]+)/);
this.setState({
// Decode the user string and parse it into JSON
user: JSON.parse(decodeURI(user_string))
});
if (Platform.OS === 'ios') {
SafariView.dismiss();
}
}
openURL(url) {
if (Platform.OS === 'ios') {
SafariView.show({
url: url,
fromBottom: true,
});
} else {
Linking.openURL(url);
}
}
render() {
return (
<Button
onPress={() => this.openURL('https://mywebsite.com/api/auth/facebook')}
title='Continue with Facebook'
...
so I guess I will have to do the authentication on URL https://mywebsite.com/api/auth/facebook and then send the user to an url that looks something like OAuthLogin://..., but I am not entirely sure how to use it.
Can anyone help me move in the right direction?
import { LoginManager, AccessToken } from 'react-native-fbsdk'; // add this file using npm i react-native-fbsdk
Create function
const onFacebookButtonPress = async () => {
// Attempt login with permissions
const result = await LoginManager.logInWithPermissions(['public_profile', 'email']);
if (result.isCancelled) {
throw 'User cancelled the login process';
}
// Once signed in, get the users AccesToken
const userInfo = await AccessToken.getCurrentAccessToken();
if (!userInfo) {
throw 'Something went wrong obtaining access token';
}
console.log('user info login', userInfo)
// Create a Firebase credential with the AccessToken
const facebookCredential = auth.FacebookAuthProvider.credential(userInfo.accessToken);
setGoogleToken(userInfo.accessToken)
// Sign-in the user with the credential
return auth().signInWithCredential(facebookCredential)
.then(() => {
//Once the user creation has happened successfully, we can add the currentUser into firestore
//with the appropriate details.
console.log('current User ####', auth().currentUser);
var name = auth().currentUser.displayName
var mSplit = name.split(' ');
console.log("mSplit ",mSplit);
let mUserDataFacebook = {
user_registration_email: auth().currentUser.email,
user_registration_first_name: mSplit[0],
user_registration_last_name: mSplit[1],
registration_type: 'facebook',
user_registration_role: "Transporter",
token: userInfo.accessToken,
user_image : auth().currentUser.photoURL,
};
console.log('mUserDataFacebook',mUserDataFacebook)
LoginWithGoogleFacebook(mUserDataFacebook) /// Call here your API
firestore().collection('users').doc(auth().currentUser.uid) //// here you can add facebook login details to your firebase authentication.
.set({
fname: mSplit[0],
lname: mSplit[1],
email: auth().currentUser.email,
createdAt: firestore.Timestamp.fromDate(new Date()),
userImg: auth().currentUser.photoURL,
})
//ensure we catch any errors at this stage to advise us if something does go wrong
.catch(error => {
console.log('Something went wrong with added user to firestore: ', error);
})
})
}
Call this function on button press onFacebookButtonPress()
For android need to setup and add facebook id in
android/app/src/main/res/values/strings.xml file
add these two lines.
YOUR_FACEBOOK_ID
fbYOUR_FACEBOOK_ID //Don't remove fb in this string value
/////////////add this code in AndroidMainfest.xml file
//////////This code add in MainApplication.java file
import com.facebook.FacebookSdk;
import com.facebook.appevents.AppEventsLogger;
/////////add code build.gradle file
implementation 'com.facebook.android:facebook-android-sdk:[5,6)'