working on a .NET MAUI app and am trying to implement Firebase Authentication with the help of WebAuthenticator in MAUI. I get to the login form in a browser, but after logging in get the error
Unable to process request due to missing initial state. This may happen if browser sessionStorage is inaccessible or accidentally cleared.
This is the code that calls the authenticator
await client.SignInWithRedirectAsync(FirebaseProviderType.Google, async uri =>
{
var options = new WebAuthenticatorOptions
{
Url = new Uri(uri),
CallbackUrl = new Uri("com.companyname.myappname://callback/"),
PrefersEphemeralWebBrowserSession= true
};
var res = await WebAuthenticator.Default.AuthenticateAsync(options);
});
I think the problem could be the callback URL, but I'm not sure how to write it differently since I'm not using a backend API. Does anyone have any suggestions?
Thanks!
P.S. This happens with bost Firebase Google auth and Facebook login
You can try to clear the chrome browser data and reload the page to see if it works. This is a known problem of firebase. You can continue to follow up this github iissue: Unableto process request due to missing initial state.
I have an SPA with Firebase backend and have integrated Google Calendar access.
To be able to authorise a user to use his/her Google Calendar I am using the gapi.auth2.authorize(params, callback) method. (this as opposed to the regular gapi.auth2.init and signIn flow because my users can link multiple Calendar accounts)
Docs: gapi.auth2.authorize
The problem I am experiencing:
Sometimes the id_token that is returned from authorize includes an email address, and sometimes it doesn't.
The id_token which is returned is a long string that can be read on the front end with a JavaScript function like so:
function parseJwt (token) {
let base64Url = token.split('.')[1]
let base64 = base64Url.replace('-', '+').replace('_', '/')
return JSON.parse(window.atob(base64))
}
When I parse the id_token, I am expecting an object including an email address. However sometimes it doesn't include the email property at all....
How can I retrieve the user's google calendar email address from this id_token in with JavaScript, so I can save it to the user's firestore DB?
Example of an expected result when parsing the id_token:
Example of an un-expected result (no email):
Possible cause:
I think that it might be related to the accounts not returning an email being a Google G-Suite account? And the ones that do return the email is a regular gmail account? But I don't know the solution.
PS:
My flow for re-authorisation for return users is to just use the same gapi.auth2.authorize but with {prompt: 'none', login_hint: 'emailaddress'} and fill in the user's saved email address. This works fine.
In case you want to authorise the JavaScript client with gapi.auth2.authorize but also require the email address the user authorised for, be sure to include email in the scope of the gapi.auth2.authorize(params, callback) parameters!!
A correct example of using JavaScript gapi for authorisation of Google calendar:
Step 1. Include in main HTML head:
<script type=text/javascript src="https://apis.google.com/js/api.js" async defer=defer></script>
Step 2. (once) Load the client: window.gapi.load('client', callbackFunction)Important: Only load the client!
Step 3. (once) Initialise the client for usage of Calendar API.
Important: Only include the discovery docs!
let calDocs = {
discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest']
}
window.gapi.client.init(calDocs)
.then(_ => {
console.log('Calendar client initialised')
})
})
},
Step 4. (once) Authorise the gapi client for API calls with gapi.auth2.authorize(params, callbackFunction)
Important: Scope is a string with spaces! Include email in the scope. Do NOT include the discovery docs here!
params = {
client_id: clientId,
scope: 'https://www.googleapis.com/auth/calendar email',
response_type: 'permission id_token'
}
You can repeat the gapi.auth2.authorize before any API call with extra params: {prompt: 'none', login_hint: 'emailaddress'} to refresh the user's access token. This will not show any prompt to the user if he already authorised once for your domain.
Problem: When used via 3rd-party app built-in browser (e.g. LINE, Twitter or Facebook messenger), the signInWithPopup returns auth/popup-blocked. The explanation by Firebase docs is:
auth/popup-blocked: Thrown if the popup was blocked by the browser, typically when this operation is triggered outside of a click handler.
Typical sequence triggering this error is: Link of my web app is sent to LINE, Twitter or Facebook messenger. When user uses mobile device and opens that link in those apps, their built-in browser is opened. Calling signInWithPopup then returns the error. The behavior is slightly different in iOS and Android but at least iOS/LINE combination results the error.
I am using Angular and building a web app. The error message is Unable to establish a connection with the popup. It may have been blocked by the browser. which comes from the firebase.js - not my own text.
When used in a normal browser, the signup works just fine.
Any ideas why the built-in browsers and signInWithPopup do not work together?
Firebase authentication should start with some user interaction, such as click on button. This solved the problem for me.
Many in-app embedded browsers block popups. I ran into the issue on instagram. Try using signInWithRedirect instead of signInWithPopup when kicking off the Oauth call.
Firebase documentation on usage of both methods can be found here - https://firebase.google.com/docs/reference/js/firebase.auth.Auth#signInWithPopup
signInWithPopup() is for the browser, however, if you're running iOS or Andriod emulator or device, you need to call signInWithCredential.
signInWithFacebook() {
if (this.platform.is('cordova')) {
return this.fb.login(['email', 'public_profile']).then(res => {
const facebookCredential = firebase.auth.FacebookAuthProvider.credential(res.authResponse.accessToken);
return firebase.auth().signInWithCredential(facebookCredential);
})
}
else {
return this.afAuth.auth
.signInWithPopup(new firebase.auth.FacebookAuthProvider())
.then(res => console.log(res));
}
}
If you're using Ionic + Firebase, you can find more info here
I have the same issue, with my web app on facebook ads campaign. I change my code from popup to redirect.
googleAuth() {
firebase
.auth()
.getRedirectResult()
.then(function(result) {
this.showLoading = true;
if (result.credential) {
var token = result.credential.accessToken;
console.log(token);
}
var user = result.user;
console.log(user);
});
this.showLoading = true;
const provider = new firebase.auth.GoogleAuthProvider();
provider.addScope("profile");
provider.addScope("email");
firebase.auth().signInWithRedirect(provider);
}
}
The problem now is save my utms from campaigns, because with redirect you lose them.
Question says it all. In Firebase, how do I confirm email when a user creates an account, or, for that matter, do password reset via email.
I could ask more broadly: is there any way to send emails out from Firebase? E.g. notifications, etc. This isn't the kind of thing you would usually do client-side.
Update
Note that this was never a very secure way of handling email verification, and since Firebase now supports email verification, it should probably be used instead.
Original answer
I solved the email verification using the password reset feature.
On account creation I give the user a temporary (randomly generated) password. I then trigger a password reset which will send an email to the user with a link. The link will allow the user to set a new password.
To generate a random password you can use code similar to this:
function () {
var possibleChars = ['abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!?_-'];
var password = '';
for(var i = 0; i < 16; i += 1) {
password += possibleChars[Math.floor(Math.random() * possibleChars.length)];
}
return password;
}
Note that this is happening on the client, so a malicious user could tamper with your logic.
This would need to be done outside of firebase. I store users at /users/ and keep a status on them (PENDING, ACTIVE, DELETED). I have a small service that monitors users of a PENDING status and sends out a confirmation email. Which has a link to a webservice I've created to update the user status to ACTIVE.
[Engineer at Firebase - Update 2014-01-27]
Firebase Simple Login now supports password resets for email / password authentication.
Each of the Simple Login client libraries has been given a new method for generating password reset emails for the specified email address - sendPasswordResetEmail() on the Web and Android, and sendPasswordResetForEmail() on iOS.
This e-mail will contain a temporary token that the user may use to log into their account and update their credentials. This token will expire after 24 hours or when the user changes their password, whichever occurs first.
Also note that Firebase Simple Login enables full configuration of the email template as well as the sending address (including whitelabel email from your domain for paid accounts).
To get access to this feature, you'll need to update your client library to a version of v1.2.0 or greater. To grab the latest version, check out https://www.firebase.com/docs/downloads.html.
Also, check out https://www.firebase.com/docs/security/simple-login-email-password.html for the latest Firebase Simple Login - Web Client docs.
As at 2016 July, you might not have to use the reset link etc. Just use the sendEmailVerification() and applyActionCode functions:
In short, below is basically how you'll approach this, in AngularJS:
// thecontroller.js
$scope.sendVerifyEmail = function() {
console.log('Email sent, whaaaaam!');
currentAuth.sendEmailVerification();
}
// where currentAuth came from something like this:
// routerconfig
....
templateUrl: 'bla.html',
resolve: {
currentAuth:['Auth', function(Auth) {
return Auth.$requireSignIn() // this throws an AUTH_REQUIRED broadcast
}]
}
...
// intercept the broadcast like so if you want:
....
$rootScope.$on("$stateChangeError", function(event, toState, toParams, fromState, fromParams, error) {
if (error === "AUTH_REQUIRED") {
$state.go('login', { toWhere: toState });
}
});
....
// So user receives the email. How do you process the `oobCode` that returns?
// You may do something like this:
// catch the url with its mode and oobCode
.state('emailVerify', {
url: '/verify-email?mode&oobCode',
templateUrl: 'auth/verify-email.html',
controller: 'emailVerifyController',
resolve: {
currentAuth:['Auth', function(Auth) {
return Auth.$requireSignIn()
}]
}
})
// Then digest like so where each term is what they sound like:
.controller('emailVerifyController', ['$scope', '$stateParams', 'currentAuth', 'DatabaseRef',
function($scope, $stateParams, currentAuth, DatabaseRef) {
console.log(currentAuth);
$scope.doVerify = function() {
firebase.auth()
.applyActionCode($stateParams.oobCode)
.then(function(data) {
// change emailVerified for logged in User
console.log('Verification happened');
})
.catch(function(error) {
$scope.error = error.message;
console.log(error.message, error.reason)
})
};
}
])
And ooh, with the above approach, I do not think there's any need keeping the verification of your user's email in your user data area. The applyActionCode changes the emailVerified to true from false.
Email verification is important when users sign in with the local account. However, for many social authentications, the incoming emailVerified will be true already.
Explained more in the article Email Verification with Firebase 3.0 SDK
What I did to work around this was use Zapier which has a built in API for firebase. It checks a location for added child elements. Then it takes the mail address and a verification url from the data of new nodes and sends them forwards. The url points back to my angular app, which sets the user email as verified.
As I host my app files in firebase, I don't need have to take care of any servers or processes doing polling in the background.
There is a delay, but as I don't block users before verifying mails it's ok. Zapier has a free tier and since I don't have much traffic it's a decent workaround for time being.
The new Firebase SDK v3 appears to support email address verification, see here (put your own project id in the link) but it doesn't appear to be documented yet.
I have asked the question on SO here
See #SamQuayle's answer there with this link to the official docs.
As noted by various others Firebase does now support account related emails but even better, as of 10 days ago or so it also supports sending any kind of email via Firebase Functions. Lots of details in the docs and example code here.
I used following code to check the email verification after creating new account.
let firAuth = FIRAuth.auth()
firAuth?.addAuthStateDidChangeListener { auth, user in
if let loggedUser = user {
if loggedUser.emailVerified == false {
loggedUser.sendEmailVerificationWithCompletion({ (error) in
print("error:\(error)")
})
}
else {
print(loggedUser.email)
}
} else {
// No user is signed in.
print("No user is signed in.")
}
}
I used MandrillApp. You can create an API key that only allows sending of a template. This way even thought your key is exposed it can't really be abused unless someone wants to fire off tonnes of welcome emails for you.
That was a hack to get myself off the ground. I'm now enabling CORS from a EC2 that uses the token to verify that the user exists before extending them a welcome via SES.
Update and solution: It turns out the problem was based on a redirect I used for my dev URL, which resulted in browsers perceiving all cookies set by my page as third-party cookies. The Facebook JS API session cookie is not set as a third-party cookie.
I'm working on an ASP.NET application with Facebook authentication. For this I use Microsofts Facebook SDK in combination with Facebooks Javascript API. Everything's working, except for Safari. Safaris default setting is to not accept third-party cookies, which results in:
I can access the Facebook session through Javascript.
I can NOT access the Facebook session server-side, because the cookie is never set and sent from Safari.
This is my client-side code related to Facebook (JS):
<div id="fb-root"></div>
<fb:login-button>Login with Facebook</fb:login-button>
<script src="http://connect.facebook.net/en_US/all.js"></script>
<script>
FB.init({ appId: 'myAppId', status: true, cookie: true, xfbml: true });
FB.Event.subscribe('auth.login', function (response) {
if (response.session) {
window.location.reload(); //Results in eternal reload of page
}
});
</script>
This is my code for fetching the user id server-side (C#):
public string FacebookUserID
{
get
{
FacebookSettings settings = new FacebookSettings();
settings.AppId = "myAppId";
settings.AppSecret = "myAppSecret";
FacebookApp app = new FacebookApp(settings);
Authorizer auth = new Authorizer(app);
return (auth.IsAuthorized()) ? app.Session.UserId : null;
}
}
I guess I'm not the only one with the same problem, but I've searched both the Facebook Developer forum and here and haven't found a solution. It's really more of a Safari problem than a Facebook-specific problem.
I've been thinking about posting the user-id back to the server through GET/POST/my own cookie, but that is an ugly solution and a potential security issue.
Any ideas?
It turns out the problem was based on a redirect I used for my dev URL, which resulted in browsers perceiving all cookies set by my page as third-party cookies. The Facebook JS API session cookie is not set as a third-party cookie.