I'm developing a web app and I use Firebase Authentication for the authentication service.
The project seems to store the authentication, since if I refresh the page, or close the browser, the user is still logged in.
However I noticed that if I don't access the app for a long time (more than 1 hour, after the night for example), the authentication gets lost.
I don't know how to debug this and how to solve this.
Following some snippets of code to better understand my implementation:
This is the function I have in my startup view to redirect the user to the right page based on auth status.
bool isUserLoggedIn() {
var user = _firebaseAuth.currentUser;
return user != null;
}
void handleStartupBasedOnAuthStatus() {
Future.delayed(const Duration(milliseconds: 1000), () async {
bool loggedInShared =
await sharedPreferences.getBoolSharedPreferences("loggedIn");
if (isUserLoggedIn() || loggedInShared) {
String ruoloValue =
await sharedPreferences.getSharedPreferences('ruolo');
(ruoloValue == Ruolo.ADMIN)
? navigationService.replaceWith(Routes.admin)
: navigationService.replaceWith(Routes.messages);
} else {
navigationService.replaceWith(Routes.login);
}
});
}
In the following function I call the onAuthStateChange to set sharedpreferences accordingly. I have the check on the timestamp because I noticed that it is triggered more time once the page is refreshed.
void listenToAuthChangesSharedPref() {
FirebaseAuth.instance.authStateChanges().listen((firebaseUser) async {
var datetimeNow = (DateTime.now().millisecondsSinceEpoch);
String oldDatetimeString =
await sharedPreferences.getSharedPreferences('previous_timestamp');
if (oldDatetimeString != null) {
var oldDatetime = (new DateTime.fromMillisecondsSinceEpoch(
int.parse(oldDatetimeString)))
.millisecondsSinceEpoch;
if (datetimeNow - oldDatetime > 1000) {
if (firebaseUser == null) {
await sharedPreferences.setBoolSharedPreferences('loggedIn', false);
} else {
await sharedPreferences.setBoolSharedPreferences('loggedIn', true);
}
await sharedPreferences.setSharedPreferences(
'previous_timestamp', datetimeNow.toString());
}
} else {
if (firebaseUser == null) {
await sharedPreferences.setBoolSharedPreferences('loggedIn', false);
} else {
await sharedPreferences.setBoolSharedPreferences('loggedIn', true);
}
await sharedPreferences.setSharedPreferences(
'previous_timestamp', datetimeNow.toString());
}
});
}
My question is: is possible that after long time currentUser and also the onAuthStateChanges gets called and the user is not logged in?
Persisting authentication state#
The Firebase SDKs for all platforms provide out of the box support for ensuring that your user's authentication state is persisted across app restarts or page reloads.
On native platforms such as Android & iOS, this behaviour is not configurable and the user's authentication state will be persisted on-device between app restarts. The user can clear the apps cached data via the device settings which will wipe any existing state being stored.
On web platforms, the user's authentication state is stored in local storage. If required, you can change this default behaviour to only persist authentication state for the current session, or not at all. To configure these settings, call the setPersistence() method (note; on native platforms an UnimplementedError will be thrown):
// Disable persistence on web platforms
await FirebaseAuth.instance.setPersistence(Persistence.NONE);
for more info:
for more info:
Related
We have Xamarin forms app integrated with azure authentication using MSAL. When we log out we are removing the accounts from the PCA and the code was executed without having any issue. But on the subsequent login, it is getting authenticated without even entering the credentials. It is logging in with previously entered credentials. Looks like the cache is not getting cleared properly.
private async void AuthenticateUser()
{
App.Scopes = new string[] { "<client_id>" + "/.default" };
var redirectUri = "msal<clientId>" + "://auth";
if (Device.RuntimePlatform == Device.iOS)
{
App.PCA = PublicClientApplicationBuilder.Create("<client_id>")
.WithIosKeychainSecurityGroup("<package_name>")
.WithRedirectUri(redirectUri)
.Build();
}
else
{
App.PCA = PublicClientApplicationBuilder
.Create(CommonHelper.ClientId)
.WithRedirectUri(redirectUri)
.Build();
}
var accounts = await App.PCA.GetAccountsAsync();
var uid = new UserIdentifier("<user_name>", UserIdentifierType.OptionalDisplayableId);
AuthenticationResult authResult;
try
{
while (accounts.Any())
{
await App.PCA.RemoveAsync(accounts.First());
accounts = (await App.PCA.GetAccountsAsync()).ToList();
}
var firstAccount = accounts.FirstOrDefault();
authResult = await App.PCA.AcquireTokenSilent(App.Scopes, firstAccount)
.ExecuteAsync();
ProceedToLogin(authResult.AccessToken);
}
catch (MsalUiRequiredException mex)
{
try
{
authResult = await App.PCA.AcquireTokenInteractive(App.Scopes)
.WithParentActivityOrWindow(App.ParentWindow)
.WithLoginHint("<user_name>")
.WithUseEmbeddedWebView(true)
.ExecuteAsync();
ProceedToLogin(authResult.AccessToken);
}
catch (Exception ex)
{
Log(ex);
}
}
}
Please find the code below which will execute during the logout.
public void Logout(string authority)
{
if (App.PCA == null)
{
App.Scopes = new string[] { "<client_id>" + "/.default" };
var redirectUri = "msal<azure_client_id>://auth";
App.PCA = PublicClientApplicationBuilder.Create("<client_id>")
.WithIosKeychainSecurityGroup("<package_name>")
.WithRedirectUri(redirectUri)
.Build();
}
var accounts = App.PCA.GetAccountsAsync().Result;
if (accounts != null)
{
while (accounts.Any())
{
App.PCA.RemoveAsync(accounts.First());
accounts = App.PCA.GetAccountsAsync().Result;
}
}
}
Also, we tried with the below code to clear the cookies. It was working fine in lower versions but again the issue is happening from iOS 14.6 and above.
var cookieStorage = NSHttpCookieStorage.SharedStorage;
foreach (var cookie in cookieStorage.Cookies)
{
cookieStorage.DeleteCookie(cookie);
}
Try adding the following line
.WithPrompt(Prompt.ForceLogin);
Example
PublicClientApplicationBuilder.Create("<client_id>")
.WithIosKeychainSecurityGroup("<package_name>")
.WithRedirectUri(redirectUri)
.WithPrompt(Prompt.ForceLogin);
.Build();
As far as I understand this from my own implementation of MSAL in .NET this is working as expected, I'm not sure how well this carries over to Xamarin. When you log a user out of they will no longer be authenticated against your application, but will keep authentication to Microsoft. When you send an unauthenticated user to the Microsoft endpoint to log back in to your application (as in your screenshot) Microsoft correctly identifies that they are still logged in to their Microsoft account, however that account is not logged in to your application. At this point Microsoft offers the list you see giving the option to use the authenticated account or choose a different one to use sign in to your application.
Their are two levels in play when authenticating against MS, authentication against MS and authentication against your application. Your application can only clear authentication against itself, not Microsoft which lets user stay logged into to other MS services (Outlook etc).
I was wondering to find a way to limit login attempt in flutter/firebase. I want the user to be able attempt login post 5 minutes of waiting. I had searched through the internet and could not find any resources to help me out. Do you have any sample code for my references ?
You can stored the time in local storage and when user login again match the stored time with current time if it's less then current time then show error.
You can use SharedPreferences to store the last attempt of the user.
Before the user can login you have to check if got a login restriction and have to pass 5 minutes.
In checkLogin you check if the user has a restriction, in this case if a login attempt time was stored. If not then he has no restriction and can login as usual. Else you check if 5 minutes have passed.
static const int fiveMinutes = 5 * 60 * 1000;
static const String lastAttemptKey = 'lastAttempt';
Future<void> checkLogin() async {
// Initialize SharedPreferences
SharedPreferences prefs = await SharedPreferences.getInstance();
// Get last login attempt
final int lastAttempt = prefs.getInt(lastAttemptKey);
// Check if is not null
if (lastAttempt != null) {
// Get time now
final int now = DateTime.now().millisecondsSinceEpoch;
// Get the difference from last login attempt
final int difference = now - lastAttempt;
// Check if 5 minutes passed since last login attempt
if (difference >= fiveMinutes) {
// User can try to login again
prefs.remove(lastAttemptKey);
await login();
} else {
// Still in limit, show error
print('You have to wait 5 minutes');
}
} else {
// First try of user login
await login();
}
}
Here the user can try to login. If it is succesfull navigate to the HomePage. Else you set the time of the login attempt to the local storage.
Future<void> login() async {
if (login.success) {
// Navigate to HomePage
} else {
// Initialize SharedPreferences
SharedPreferences prefs = await SharedPreferences.getInstance();
// Store attempt time
prefs.setInt(lastAttemptKey, DateTime.now().millisecondsSinceEpoch);
}
}
I'm using firebase anonymous authantication for my unity project.
As i always did when project is started i'm sending request to firebase for authantication,
but on my last project (which uses firebase sdk 6.16.0) my request creates new user everytime.
Here is some code about how i'm sending my request
Firebase.Auth.FirebaseAuth auth = Firebase.Auth.FirebaseAuth.DefaultInstance;
auth.SignInAnonymouslyAsync().ContinueWith((task =>
{
if (task.IsCanceled)
{
Debug.Log("task cancelled");
return;
}
if (task.IsFaulted)
{
Debug.Log("task cancelled");
return;
}
if (task.IsCompleted)
{
Firebase.Auth.FirebaseUser userr = task.Result;
firebaseUserId = userr.UserId;
Debug.Log("firebaseUserId");
Debug.Log(firebaseUserId);
//every opening returns new uniq id here.
}
}));
On firebase authantication panel i only activated anonymous login. any suggestions?
Or is there any way to downgrade unity firebase version? i've tried to import old version which i was using on my last game (sdk 6.15.2) but there is some errors on resolver.
Basically, every time you call SignInAnonymouslyAsync you'll create a new user and the last one will be basically lost (it's more or less a random hash - anonymous as it's name suggests).
I'll typically do something like:
using System;
using Firebase.Auth;
using UnityEngine;
using UnityEngine.Events;
public class Login : MonoBehaviour
{
public UnityEvent OnSignInFailed = new UnityEvent();
public UserSignedInEvent OnUserSignedIn = new UserSignedInEvent();
public async void TriggerLogin()
{
var auth = FirebaseAuth.DefaultInstance;
var user = auth.CurrentUser;
if (user == null)
{
try
{
user = await auth.SignInAnonymouslyAsync();
}
catch (Exception e)
{
Debug.LogException(e);
OnSignInFailed.Invoke();
return;
}
}
// user definitely should not be null!
if (user == null)
{
OnSignInFailed.Invoke();
Debug.LogWarning("User still null!?");
return;
}
var userName = user.UserId;
OnUserSignedIn.Invoke(userName);
Debug.Log($"Logged in as {userName}");
}
[Serializable]
public class UserSignedInEvent : UnityEvent<string>
{
}
}
Note that for this code snippet, TriggerLogin is a public method so I can chain it off of a UnityEvent in the Unity editor.
Try and Put it some kind of check to find if used is already logged in. If yes, then do a silent login, if no then use anonymous login.
Currently you are straightaway logging in user even if they logged in last time they opened the Application.
Try this link: https://github.com/firebase/quickstart-unity/issues/266#issuecomment-447981995
I'm currently trying to get push notifications working for my mobile app using Azure Notification Hubs. Android is working fine and the initial iOS set up in AppDelegate works ok with a sample tag.
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
if (deviceToken == null)
{
return;
}
SBNotificationHub hub = new SBNotificationHub(CommonConstants.LISTEN_CONNECTION_STRING, CommonConstants.NOTIFICATION_HUB_NAME);
// update registration with Azure Notification Hub
hub.UnregisterAll(deviceToken, async (error) =>
{
if (error != null)
{
System.Diagnostics.Debug.WriteLine($"Unable to call unregister {error}");
return;
}
string[] tags = new[] { "iostestpush" };
NSSet userTags = new NSSet(tags);
hub.RegisterNative(deviceToken, userTags, (error) =>
{
if (error != null)
{
System.Diagnostics.Debug.WriteLine($"Unable to call register {error}");
return;
}
});
var templateExpiration = DateTime.Now.AddDays(120).ToString(System.Globalization.CultureInfo.CreateSpecificCulture("en-US"));
hub.RegisterTemplate(deviceToken, "defaultTemplate", CommonConstants.APN_TEMPLATE_BODY, templateExpiration, userTags, (errorCallback) =>
{
if (errorCallback != null)
{
System.Diagnostics.Debug.WriteLine($"RegisterTemplateAsync error: {errorCallback}");
}
});
});
}
The issue I'm having is I need to register the UserId after a successful login. So I set up a service with the above code, saved the token to the device as string so it can be retrieved in the service and turned back into an NSData token
NSData deviceToken = new NSData(token, NSDataBase64DecodingOptions.None);
After a successful login I send the token string and the tag array to my service.
string[] userTag = new[] { loginResponse.UserId.ToString() };
await this._azureReg.SendRegistrationToServer(deviceToken, userTag);
Which, other than turning the token back into NSData and the user tag into an NSSet, is the same as above other than the name change. But Azure is claiming there is no registration even though my output shows
Registered for push notifications with token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
I thought it was the string conversion back and forth, so tested that in the AppDelegate and it worked fine.
So, I'm at a loss at how to register the UserId after a successful login and why it works in one place but not the other.
I hope that's clear and thanks for any advice in advance.
You probably ran into the same bug as me and several others.
Basically SBNotificationHub method overloads like UnregisterAll and RegisterTemplate with the callback signature do not work when you use them off the main thread, using the libraries to date. I was also using a Service for the same purpose (to handle push across platforms with different tags, especially for user id) but my implementation involved switching off the main thread for this.
The bug we logged and is now being addressed is here: https://github.com/Azure/azure-notificationhubs-ios/issues/95
The solution, for now, is to ditch SBNotificationHub completely. The Xamarin / Azure documentation is out of date, and SBNOtificationHub is legacy code. The recommended library is MSNotificationHub. https://github.com/azure/azure-notificationhubs-xamarin
As workarounds you can use the SBNotificationHub method overloads that do not involve callbacks (they return an error message instead) or the workaround in the 95 issue above.
When I open app by tapping on FCM push notification, The API service calls I am making by using await keyword those are not working. Entire app not returning data.
Code for API calling
var result = await objHomework.GetHomeWorksForStudentPagesAsync(studentId.ToString());
result returning null. if app already open, everything working fine. See the Image below screenshot of app
Notification messages are delivered to OnMessageReceived callback only when the app is in the foreground.
Override the HandleIntent Method of the FirebaseMessageService to work for background as well
public override void HandleIntent(Intent intent)
{
try
{
if (intent.Extras != null)
{
var builder = new RemoteMessage.Builder("MyFirebaseMessagingService");
foreach (string key in intent.Extras.KeySet())
{
builder.AddData(key, intent.Extras.Get(key).ToString());
}
this.OnMessageReceived(builder.Build());
}
else
{
base.HandleIntent(intent);
}
}
catch (Exception)
{
base.HandleIntent(intent);
}
}
Actually, I was missing some keys which is necessary for service call authentication in my project. I am getting those keys in MaiActivity but notification click even starting app from somewhere else therefore keys values was null and service calls was not happening.