Limiting Login attempt in Flutter? - firebase

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);
}
}

Related

Firebase Auth with unity creates new user in every start

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

Firebase gets logged out after long time in Flutter Web

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:

Flutter Firebase Auth returns null, then returns the correct value

I have an issue with utilising the current user's id (UID). The following code 'works' however there are instances where the _currentUID first outputs a 'null' before outputting the correct value.
class _ContactsScreenState extends State<ContactsScreen> {
String _currentUID;
#override
initState() {
super.initState();
loadCurrentUser();
}
loadCurrentUser() async {
var currentUID = await _getCurrentUID();
setState(() {
this._currentUID = currentUID;
});
}
Future<String> _getCurrentUID() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
return user.uid;
}
#override
Widget build(BuildContext context) {
if (_currentUID == null){
print("current UserID = null");
} else {
print("current UserID = $_currentUID");
}
return StreamBuilder(
...
So this is actually working fine, outputs the results as expected, however upon inspection the printed output is as follows:
flutter: current UserID = null // why is it printing null?
flutter: current UserID = abcd1234abcd //correct
What is unusual is that this will only occur when the user visit's the screen for the 2nd time. The first time the screen/page loads it will correctly output 'only' the actual Current User ID. It when the user goes back to the same page it then will print current user twice (as shown above).
This is perfectly normal.
loadCurrentUser is async, so will complete some time after the instance if _ContactsScreenState is created. Only then will _currentUID be assigned.
If the framework calls build before that assignment, then it will be null. It's normal to have build simply return Container or a progress indicator if it's null. Once it it assigned, build will be called again. This time it will not be null and you can build the 'normal' screen.

How to refresh claim of a different user than the one logged in during the current request?

I use the following code to update the claims of a user in my web application. However, to update the claims/cookie of this user, I want to force him to login again. So essentially I want to expire his cookie after I update the claims. Any idea how to do this?
await _signInManager.RefreshSignInAsync(user); is the first thing I tried, but fails because I'm updating the claims of another user (the one that is currently logged in) :)
All other examples I found are more or less the same as RefreshSignInAsync and do not deal with the fact that I'm updating the claims of another user.
public async Task<IActionResult> AddClaimPost(string id)
{
var user = _context.ApplicationUser
.SingleOrDefault(m => m.Id == id);
foreach(var item in Request.Form)
{
if (item.Key.Contains("Claim"))
{
if (item.Value.Contains("true"))
{
if (!User.HasClaim(item.Key, item.Key))
{
var result = await _userManager.AddClaimAsync(user, new Claim(item.Key, item.Key));
}
}
else
{
var result2 = await _userManager.RemoveClaimAsync(user, new Claim(item.Key, item.Key));
}
}
}
await _signInManager.RefreshSignInAsync(user);
return RedirectToAction("Overview");
}
After searching a few days I discovered that what I want is not possible. You cannot force logging the user out without putting the cookie timespan to 0
options.Cookies.ApplicationCookie.ExpireTimeSpan = 0;
In this case it will check the cookie every time the user makes a request. With the following code you can than force the user to login again:
await _userManager.UpdateSecurityStampAsync(user);
I don't recommend the 0 expire timespan approach.
If you have a redis server (or any other persistent data store that is performant) you can do something like:
await redis.StringSetAsync("refresh_login_" + user.Id, "1", null);
Then on every page load you will check this redis value and refresh the signin if the key is set for you:
Filters/BaseActionFilter.cs:
public class BaseActionFilter: IAsyncActionFilter, IAsyncPageFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{ // For classic controllers
await PerformPageTasks(context.HttpContext);
await next();
}
public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context,
PageHandlerExecutionDelegate next)
{ // For razor pages in Areas/
if ((context.HandlerInstance is PageModel page))
{
await PerformPageTasks(context.HttpContext);
}
await next.Invoke();
}
public async Task PerformPageTasks(HttpContext context)
{
var signinManager = context.RequestServices.GetService<SignInManager<MyWebUser>>();
if (signinManager.IsSignedIn(context.User))
{
var cache = context.RequestServices.GetService<IDistributedCache>();
var redis = (await ((RedisCache)cache).GetConnectionAsync()).GetDatabase();
var userManager = context.RequestServices.GetService<UserManager<MyWebUser>>();
var user = await userManager.GetUserAsync(context.User);
if ((await redis.StringGetAsync("refresh_login_" + user.Id)) == "1")
{
await redis.KeyDeleteAsync("refresh_login_" + user.Id);
// refresh the user
await signinManager.RefreshSignInAsync(user);
}
}
}
public async Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
{
await Task.CompletedTask;
}
}
Startup.cs:
services.AddMvc(o =>
{
...
o.Filters.Add(new BaseActionFilter());
}).AddHybridModelBinder();
If you only use traditional controllers or Areas/ razor pages then you can adapt the code accordingly.
Note this requires the user to make an additional page load before the claims are set, so for things like [Authorize] you would need to put this code earlier in the chain and I'm not sure exactly how to do that.

Is firebase checking for user first time

On initial load, firebase tells me, if user is logged in by firing event like this:
firebase.auth().onAuthStateChanged(func...)
I want to check, if firebase is still checking it. Like show spinner when page loads, wait for firebase to check user and then show app or login/register form, considering user found or not.
Now I just have to show page, then init firebase, and later, if firebase founds user, redirect to app.
Swift 4
Method 1
Check if the automatic creation time of the user is equal to the last sign in time (Which will be the first sign in time if it is indeed their first sign in)
//Current user metadata reference
let newUserRref = Auth.auth().currentUser?.metadata
/*Check if the automatic creation time of the user is equal to the last
sign in time (Which will be the first sign in time if it is indeed
their first sign in)*/
if newUserRref?.creationDate?.timeIntervalSince1970 == newUserRref?.lastSignInDate?.timeIntervalSince1970{
//user is new user
print("Hello new user")
}
else{
//user is returning user
print("Welcome back!")
}
Method 2
ALTERNATIVELY, you can set a global var in App Delegate. Most apps I've worked on use automatic Firebase login if the user already exists; meaning that it will not update the lastSignInDate value and thus still show the user as a new user.
So start by creating a variable in AppDelegate above the class like so:
var newUser = false
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate{
Then whenever you call your function to create a new Firebase user, set newUser to true:
newUser = true
Lastly, make an if statement that filters which user your main controller is receiving:
Override func viewDidLoad() {
super.viewDidLoad()
if newUser == true{
print("welcome new user")
showOnboarding()
}
else{
print("Welcome back!")
}
}
Now anytime an existing user logs in, the variable will remain false
The listener passed to onAuthStateChanged will be called with an argument that is either null or the User instance.
So it's safe to assume that Firebase is checking the authentication status between your calling of initializeApp and the listener for onAuthStateChanged being called. Display the spinner when you call initializeApp and hide it when the listener is called.
You can use the FIRAuthDataResult object returned in the successful sign-in to determine whether the user is new from the additionalUserInfo property:
Auth.auth().signIn(with: credential) { (authResult, error) in
if let error = error {
print(error.localizedDescription)
return
}
// User is signed in
// ...
//Check if new user
if let isNewUser: Bool = authResult?.additionalUserInfo?.isNewUser {
if isNewUser {
print("new user")
}
}
}

Resources