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 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
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']);
//});
}
});
});
});
}
}
You should use the Database (Realtime Database or Firestore) to save user’s devices and check from there if he can login.
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.