is there a way in Firebase to limit the number of logins by the same user at the same time, says I want to put a limit of 3 devices at a time for one single account, how can I achieve that ?
3 Answers
That is not supported by Firebase. The best you can do is keep track of the token auth_time. This is the time of sign-in. You would keep a queue of 3 entries for that. Each time you sign-in a user, send their ID token for verification, add the auth_time to the queue (if it is not there already) and dequeue the oldest auth_time in the queue if it exceeds it maximum size (3) . You would only allow access to data for ID tokens with auth_times within that queue.
- 29,642
- 4
- 69
- 76
-
I don't think that it solve the problem because the tokens expires after an hour, so if a client open his app after that he will renew his token and the comparison will fail. – Eloise85 Jul 11 '18 at 20:39
-
Token refresh will only update the `iat` field. The `auth_time` is the time the user signed in. That will remain the same no matter how often the token refreshes. – bojeil Jul 12 '18 at 00:47
I came around similar situation and then I implemented bojeil approach for limiting simultaneous login using one user credentials.
I have maintained following array field and number field in user document.
{
allowed_auth_times : [
],
max_granted_login : 3
}
Add entry into this array on
- user signup.
- If another person logs into the system using same credentials (either on same or different device) if
size(allowed_auth_times) < max_granted_login
If size(allowed_auth_times) == max_granted_login
then remove oldest auth time, (allowed_auth_times[0]) and then add entry of new auth time.
This way you will make sure that, at any given point of time, limited users will be able to use the system. I have implemented this using Angular + AngularFire2 I will be more than happy if anyone can provide suggesting for improving this. Thanks in advance.
Authentication Component :
trySignInWithEmailAndPassword() {
// Actual Signin process starts here.
this._firebaseAuthenticationService.signInUserWithEmailAndPassword(this.userCredentials.value)
.then((user) => {
this._firebaseAuthenticationService.OTCheckAndUpdateDBAuthTime().then((result) => {
bootbox.hideAll();
if (result === true) {
this._router.navigate(['/home']);
}
}, (err) => {
bootbox.hideAll();
bootbox.alert("Error Occured. Please contact support team with screenshot. " + err);
});
}).catch((err) => {
console.error("Login failed. Redirecting user to authentication page.");
bootbox.alert("Some Error Occured While Authenticating " + this.userCredentials.get("email").value);
this._router.navigate(['/authentication']);
});
}
FirebaseAuthService
import { Injectable, OnInit } from '@angular/core';
import { AngularFireAuth } from '@angular/fire/auth';
import * as firebase from 'firebase/app';
import { Router } from '@angular/router';
import { Observable } from 'rxjs';
import { AngularFirestore } from '@angular/fire/firestore';
import { environment } from 'src/environments/environment';
import { first, take } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class FirebaseAuthenticationService implements OnInit {
ngOnInit(): void {
}
uid: string = "";
constructor(private _angularFireAuth: AngularFireAuth,
private db: AngularFirestore,
private _router: Router) { }
trySignOut() {
return new Promise<any>((resolve, reject) => {
this._angularFireAuth.auth.signOut().then(() => {
resolve();
}, (error) => {
reject(error);
}
);
});
}
getCurrentUserUID(): string {
if (this._angularFireAuth.auth.currentUser != undefined || this._angularFireAuth.auth.currentUser != null) {
let uid = this._angularFireAuth.auth.currentUser.uid;
if (uid == undefined || uid == null) {
return "";
} else {
return uid;
}
}
return this._angularFireAuth.auth.currentUser.uid;
}
getCurrentUserUID2(): Observable<firebase.User> {
return this._angularFireAuth.authState.pipe(first());
}
createUserWithEmailAndPassword(userCredentials: any) {
return new Promise<any>((resolve, reject) => {
this._angularFireAuth.auth.createUserWithEmailAndPassword(userCredentials.email, userCredentials.password)
.then((userData) => {
userData.user.getIdTokenResult().then((a) => {
console.log("Auth Time : " + a.authTime);
resolve(userData.user);
});
});
});
}
signInUserWithEmailAndPassword(userCredentials: any) {
return new Promise<any>((resolve, reject) => {
this._angularFireAuth.auth.signInWithEmailAndPassword(userCredentials.email, userCredentials.password)
.then((userData) => {
console.log(userData);
// If email is not verified then send verification email every time.
if (this._angularFireAuth.auth.currentUser.emailVerified == false) {
this.sendEmailVerification();
}
userData.user.getIdTokenResult().then((a) => {
console.log("Auth Time : " + a.authTime);
resolve(userData.user);
});
}, err => {
console.error("Error Occured During Signin user with email and password in auth service.");
console.error(err);
reject(err);
});
});
}
// This function checks entry in allowed_auth_times [] depending on max_logins : in user profile.
OTCheckAndUpdateDBAuthTime() {
return new Promise<any>((resolve, reject) => {
this._angularFireAuth.authState.subscribe((userAuthState) => {
if (userAuthState.uid != null || userAuthState.uid != undefined || userAuthState.uid != "") {
const user_doc_id = userAuthState.uid;
this.db.collection("users").doc(user_doc_id).snapshotChanges().subscribe((docData) => {
let doc: any = docData.payload.data();
let allowed_auth_times_arr: string[] = doc.allowed_auth_times;
let max_granted_login: number = parseInt(doc.max_granted_login);
this.getAuthTime().then((currentUserAuthTime) => {
if (allowed_auth_times_arr && allowed_auth_times_arr.includes(currentUserAuthTime)) {
resolve(true);
} else {
if (allowed_auth_times_arr) {
if (allowed_auth_times_arr.length == max_granted_login) {
allowed_auth_times_arr.splice(0, 1); // Delete Oldest Entry
}
allowed_auth_times_arr.push(currentUserAuthTime);
this.updateDBAuthTimesArr(userAuthState.uid, allowed_auth_times_arr).then(() => {
resolve(true);
}, (error) => {
bootbox.alert("Error Occured While Updating Auth Times. Please take screenshot of this and contact June Support team. " + error);
reject(false);
});
}
}
});
});
}else{
console.error("Authentication Service > OTCheckAndUpdateDBAuthTime > userAuthState is blank");
}
});
});
}
// Update
updateDBAuthTimesArr(uid: string, allowed_auth_times_arr: string[]) {
return this.db.collection(environment.collctn_users).doc(uid).update({
allowed_auth_times: allowed_auth_times_arr
});
}
// Get Auth Time of Currently Signed in User
getAuthTime() {
return new Promise<any>((resolve, reject) => {
try {
this._angularFireAuth.authState.pipe(take(1)).subscribe((userAuthState) => {
if (userAuthState) {
userAuthState.getIdTokenResult().then((tokenResult) => {
console.log("Token result obtained. ");
console.log("Auth time obtained : " + tokenResult.authTime);
resolve(tokenResult.authTime);
});
} else {
console.error("Blank UserAuthState Captured.");
reject(null);
}
});
} catch (err) {
console.error("Error Occured while obtaining the auth time of the user.");
reject(null);
}
});
}
**// You will be using this in other components. If there is entry of authTime in allowed_auth_times array then keep this user signed in, otherwise signout forcefully.**
validateAuthTime(uid: string): Observable<boolean> {
return new Observable<any>((observer) => {
this.db.collection(environment.collctn_vendor_list).doc(uid).snapshotChanges().subscribe((docData) => {
let doc: any = docData.payload.data();
this.getAuthTime().then((currentUserAuthTime) => {
if (doc.allowed_auth_times.includes(currentUserAuthTime)) {
observer.next(true);
} else {
console.log("ValidateAuthTime > else : Observer returning false.");
observer.next(false);
//this.trySignOut().then(() => {
//this._router.navigate(['/authentication']);
//});
}
});
});
});
}
}
- 1
- 1
- 459
- 5
- 7
You should use the Database (Realtime Database or Firestore) to save user’s devices and check from there if he can login.
- 923
- 2
- 15
- 41