So I'm using Firebase-UI to authenticate and sign in users, I need to use the account chooser in order for them to sign in to a different Google account (not using the account chooser results in it auto-signing them in), however, I want to either prevent it from displaying + saving accounts, or remove them on sign out.
And this is the Firebase-UI web I'm using: Firebase UI web
This isn't a huge issue when the application is running on a user's machine, however, it will also be running on a public machine with many users signing in an out, and we can't have them saved as an easy one-click sign in. The biggest security issue is the fact that I can also log into their emails once they've authenticated with Google. We want it to forget them once they sign out.
My sign-in flow:
<script type="text/javascript">
// FirebaseUI config.
var uiConfig = {
callbacks: {
signInSuccess: function (user, credential, redirectUrl) {
var userSignIn = {
displayName: user.displayName,
email: user.email,
emailVerified: user.emailVerified,
photoURL: user.photoURL,
uid: user.uid,
phoneNumber: user.phoneNumber
};
/* POST signed in user to Login Controller*/
var csrfToken = $('input[name="csrfToken"]').attr('value');
$.ajaxSetup({
beforeSend: function(xhr) {
xhr.setRequestHeader('Csrf-Token', csrfToken);
}
});
$.ajax({
url: '/signedIn',
type: 'POST',
data: JSON.stringify(userSignIn),
contentType: 'application/json',
error: function(err) {
console.log(err);
}
});
return true;
}
},
signInSuccessUrl: '/Dashboard',
signInOptions: [{
provider: firebase.auth.GoogleAuthProvider.PROVIDER_ID,
scopes: ['https://www.googleapis.com/auth/calendar']
}],
// Terms of service url.
tosUrl: '/Terms'
};
// Initialize the FirebaseUI Widget using FirestoreDB.
var ui = new firebaseui.auth.AuthUI(firebase.auth());
// The start method will wait until the DOM is loaded.
ui.start('#firebaseui-auth-container', uiConfig);
</script>
Sign-out flow:
initApp = function () {
firebase.auth().onAuthStateChanged(function (user) {
if (user) {
// User is signed in.
if (window.location.pathname === "/Login" || window.location.pathname === "/") {
window.location.href = '/Dashboard';
}
$('#sign-out').show();
} else {
// User is signed out.
$('#sign-out').hide();
disableLinks();
switch(window.location.pathname){
case "/Login":
case "/Terms":
case "/Help":
break;
default:
window.location.href = '/Login';
}
}
}, function (error) {
console.log(error);
});
};
window.addEventListener('load', function () {
initApp();
document.getElementById('sign-out').addEventListener('click', function () {
firebase.auth().signOut().then(function() {
sessionStorage.clear();
localStorage.clear();
window.location = "/Logout";
}).catch(function(error) {
console.log(error);
});
});
});
On sign out from Firebase Auth, redirect to Google single sign out URL:
firebase.auth().signOut()
.then(function() {
window.location.assign('https://accounts.google.com/Logout');
})
.catch(function(error) {
console.log(error);
});
Related
I have been working on a oauth2 flow for spotify by following this similar tutorial by the Firebase team for Instagram HERE
I am able to submit my credentials and return the user code and state in the url, but when I run the method to submit the code to return an auth token, the auth token that I print to console in the Firebase functions returns: Auth Token Error Not Found. Here's my workflow:
Here's the Spotify docs
FIRST, I have a function to configure my spotifyOAuth:
function spotifyOAuth2Client() {
// Spotify OAuth 2 setup
const credentials = {
client: {
id: functions.config().spotify.clientid,
secret: functions.config().spotify.clientsecret,
},
auth: {
tokenHost: 'https://accounts.spotify.com',
authorizePath: '/authorize'
},
};
return require('simple-oauth2').create(credentials);
}
I use that function in this Firebase function that is called using https://us-central1-<my project string>.cloudfunctions.net/redirect:
exports.redirect = functions.https.onRequest((req, res) => {
const oauth2 = spotifyOAuth2Client();
cookieParser()(req, res, () => {
const state = req.cookies.state || crypto.randomBytes(20).toString('hex');
console.log('Setting verification state:', state);
res.cookie('state', state.toString(), {
maxAge: 3600000,
secure: true,
httpOnly: true,
});
const redirectUri = oauth2.authorizationCode.authorizeURL({
redirect_uri: OAUTH_REDIRECT_URI,
//scope: OAUTH_SCOPES,
state: state,
});
console.log('Redirecting to:', redirectUri);
res.redirect(redirectUri);
});
});
The code above returns a url string with the proper parameters, the following code block is where my code breaks, I have another cloud function that runs after being redirected from the res.redirect(redirectUri) above. And when I try to run the getToken() method, it appears to not return anything because I hit the catch block instead? This is where I observe the Auth Token Error Not Found.
const oauth2 = spotifyOAuth2Client();
try {
return cookieParser()(req, res, async () => {
console.log('Received verification state:', req.cookies.state);
console.log('Received state:', req.query.state);
if (!req.cookies.state) {
throw new Error('State cookie not set or expired. Maybe you took too long to authorize. Please try again.');
} else if (req.cookies.state !== req.query.state) {
throw new Error('State validation failed');
}
console.log('Received auth code:', req.query.code);
console.log(OAUTH_REDIRECT_URI);
// Get the access token object (the authorization code is given from the previous step).
const tokenConfig = {
code: req.query.code,
redirect_uri: 'http://localhost:8100/popup'
};
// Save the access token
try {
const result = await oauth2.authorizationCode.getToken(tokenConfig)
const accessToken = oauth2.accessToken.create(result);
console.log('inside try');
console.log(result);
console.log(accessToken);
} catch (error) {
console.log('Access Token Error', error.message);
}
I've double checked my spotify client/secret credentials in the config, what is going wrong with this OAuth2 flow?
Resolved my issue, I was not using the correct endpoints:
const credentials = {
client: {
id: functions.config().spotify.clientid,
secret: functions.config().spotify.clientsecret,
},
auth: {
tokenHost: 'https://accounts.spotify.com',
authorizePath: '/authorize',
tokenPath: '/api/token'
},
};
I use nativescript-plugin-firebase to connect with Firebase. It does work when create new user / login / CRUD operations when app is online mode.
When the app is offline mode, Login authentication / Create new user is not working rest of the above mentioned things are working.
My code base:
app.component.ts
ngOnInit(): void {
firebase.init({
persist: true,
storageBucket: 'gs://**************/',
onAuthStateChanged: (data: any) => {
console.log(JSON.stringify(data))
if (data.loggedIn) {
BackendService.token = data.user.uid;
}
else {
BackendService.token = "";
}
}
}).then(
function (instance) {
console.log("firebase.init done");
},
function (error) {
console.log("firebase.init error: " + error);
}
);
firebase.keepInSync(
"/users", // which path in your Firebase needs to be kept in sync?
true // set to false to disable this feature again
).then(
function () {
console.log("firebase.keepInSync is ON for /users");
},
function (error) {
console.log("firebase.keepInSync error: " + error);
}
);
in service file
login(user: User) {
return firebase.login({
type: firebase.LoginType.PASSWORD,
email: user.email,
password: user.password
}).then((result: any) => {
BackendService.token = result.uid;
return JSON.stringify(result);
}, (errorMessage: any) => {
// alert(errorMessage);
alert('Email address / Password is wrong!!!');
});
}
Anyone help me how login will work in when offline mode.
I want to let my meteor users login through my react-native app.
I have one website built by Meteor/reactjs and the other is an App using android react-native and they both share the same Mongo database.
My meteor website uses bcrypt (meteor accounts-password), and my react-native app where I tried to use SHA256 and bcrypt so when I log in through my react-native app which is connected to the database, the hashed passwords do not match and I get an error.
I'm just getting really confused as to how to hash my react-native password to get it to the match the meteor users' hashed password. Any help would be great, cheers.
This is the front end
sendAjax = () => {
const m = encodeURIComponent(this.state.email);
const p = encodeURIComponent(this.state.password);
const hashDigest = sha256(p);
const requestBody = `email=${m}&password=${hashDigest}&mphone`;
//POST
fetch("http://**************/users/auth", {
method: "POST",
mode: "cors",
headers: {
"Accept": "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
body: requestBody
}).then(function (res, next) {
console.log("fetch request ", JSON.stringify(res.ok));
if(res.ok){
res.json().then(function (json) {
console.info(json);
console.info(json.verifystate);
if(json.verifystate){
Alert.alert("Login success");
Actions.leaderBoard();
}else{
Alert.alert("Login fail, please try again");
}
});
}else{
Alert.alert('Noted','request failed',[{text: 'confirm', onPress: () => console.log('OK Pressed!')},]);
next();
}
})
.catch(function (e) {
console.log("fetch fail");
Alert.alert('Noted','system error',[{text: 'confirm', onPress: () => console.log('OK Pressed!')},]);
});
}
This is my backend
app.post('/users/auth', function(req, res) {
loginData(db, req.body.email, req.body.password, function(result){
if(result == 1){
console.log("find the result!");
res.send({"verifystate":true});
}else{
console.log('cannot find it');
}
});
});
......
var loginData = function(db, email, myPlaintextPassword, mphone, callback){
var collectionUser = db.collection('users');
bcrypt.genSalt(saltRounds, function(err, salt) {
bcrypt.hash(myPlaintextPassword, salt, function(err, hash) {
var queryStr = {"emails.address": email, "services.password.bcrypt": hash};
collectionUser.count(queryStr, function(err, result) {
if(err)
{
console.log('Error2:'+ err);
}
callback(result);
});
});
});
};
I'm doing an app with Ionic Framework and Firebase. I made a custom login to get data inside Firebase, but every single time the app is restarted I need to login again. How can I persist the login? The user should login the first time, and not need to do it again.
Here is my service:
(function() {
'use strict';
angular
.module('mytodo.login')
.factory('LoginService', LoginService);
LoginService.$inject = ['$state', '$ionicLoading', '$firebaseAuth', '$firebaseObject','$rootScope', '$timeout', 'fb', '$q'];
function LoginService($state, $ionicLoading, $firebaseAuth, $firebaseObject, $rootScope, $timeout, fb, $q){
var service = {
CustomLogin: CustomLogin,
GetCurrentUser: GetCurrentUser,
RegisterUser: RegisterUser,
};
return service;
function CustomLogin(email, password) {
if(email ==null | password == null){
console.log('Preencha todos os campos!');
return;
}
$ionicLoading.show({
showBackdrop: false,
template: '<p>Carregando...</p><ion-spinner icon="android" style="stroke: #1d9c9e;fill:#1d9c9e;"></ion-spinner>'
});
$firebaseAuth().$signInWithEmailAndPassword(email, password).then(function(authData) {
$rootScope.currentUser = GetCurrentUser(authData.uid);
$timeout(function() {
$ionicLoading.hide();
$state.go('tab.todo', {});
}, 1000);
}).catch(function(error) {
showToast();
$ionicLoading.hide();
console.log(error);
});
}
function showToast(){
ionicToast.show('Usuário ou senha inválido', 'middle', false, 1500);
}
function GetCurrentUser(userId) {
var query = fb.child('/users/' + userId);
var currentUser = $firebaseObject(query)
return currentUser;
}
function SaveUser(authData) {
console.log(authData.uid);
var deffered = $q.defer();
var uid = authData.uid;
var user = {
displayName: authData.displayName,
name: authData.displayName,
photoURL: authData.photoURL,
email: authData.email,
emailVerified: authData.emailVerified,
providerId: authData.providerData[0].providerId
};
var ref = fb.child('/users/' + uid);
ref.once("value")
.then(function(snapshot) {
if (snapshot.exists()) {
console.log('User already exists');
} else {
ref.set(user);
}
deffered.resolve(snapshot);
});
return deffered.promise;
};
function RegisterUser(user) {
var deffered = $q.defer();
$ionicLoading.show();
$firebaseAuth().$createUserWithEmailAndPassword(user.email, user.password).then(function(authData) {
var newUser = {
name: user.name,
email: user.email,
providerId: authData.providerData[0].providerId
};
var userId = authData.uid;
var ref = fb.child('/users/' + userId);
ref.once("value")
.then(function(snapshot) {
if (snapshot.exists()) {
//console.log('User already exists');
} else {
ref.set(newUser).then(function(user){
$rootScope.currentUser = GetCurrentUser(userId);
})
}
deffered.resolve(snapshot);
CustomLogin(user.email, user.password);
});
}).catch(function(error) {
$ionicLoading.hide();
var errorCode = error.code;
console.log(errorCode);
if(errorCode === 'auth/weak-password')
ionicToast.show('Erro, a senha precisa ter no mínimo 6 digitos.', 'middle', false, 3000);
if(errorCode === 'auth/email-already-in-use')
ionicToast.show('Erro, o email: ' + user.email + ' já existe em nossa base de dados.', 'middle', false, 3000);
})
return deffered.promise;
};
}
})();
To re-iterate the point of don't persist the login yourself, firebase does this for you. I am referencing this from typescript FYI.
In the official docs() :
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL)
Where local is on disk.
Then later in your code all you need to do is subscribe to the onAuthStateChanged observable.
this.firebase.auth.onAuthStateChanged(user => {
if (user){
Do not persist the plain text password yourself!!!! Firebase persists a user with uid, session API keys etc.
Just follow the Firebase docs. Persisting plain text password will result in a bad security audit.
Newer version
Initialize the app like this to keep the user logged in even after the browser is closed and reopened on the same device.
import { initializeApp } from 'firebase/app';
import { getAuth, browserLocalPersistence, setPersistence } from 'firebase/auth'
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
(async () => {
await setPersistence(auth, browserLocalPersistence);
})();
To get the user object you can use React Firebase Hooks:
import { useAuthState } from 'react-firebase-hooks/auth';
const [user, loading, error] = useAuthState(auth);
You shouldn't persist username and password to storage, if you have to then at least store the password as a hash.
Firebase has the following for signing in again:
firebase.auth().onAuthStateChanged(user => {
});
I've figured out how to do this. Maybe it's not the most correct anwser for it, but it worked for me. I used localSotrage to store the username and password. I could store the tolken as well, but I want to create a "remember password" screen.
When I do my first login I do this in my service.
service.js when I store the user data;
localStorage.setItem("uPassword",password);
localStorage.setItem("uEmail",email);
And I add the following if statement in my controller. If i already did the login, I use the e-mail and password to login again. If I dont, I wait to user press the button and call de function in my service.
controller.js if statement:
if(localStorage.getItem("uEmail")!==undefined && localStorage.getItem("uPassword")!==undefined) {
LoginService.CustomLogin(localStorage.getItem("uEmail"),localStorage.getItem("uPassword"))
}
I can add users in Firebase console -> Auth but I can't do anything more than setting an email and password for them.
Could I in some way set for them displayName?
I guess if you just want to update users profile:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
user.updateProfile({
displayName: "Random Name"
}).then(function() {
// Update successful.
}, function(error) {
// An error happened.
});
} else {
// No user is signed in.
}
});
Additionally: https://firebase.google.com/docs/auth/web/manage-users
When you create a user you create it only with email and password but you can and the displayName in the promise, then inside the .then() method you call the updateProfile method and you are ready, right down is the code:
onSubmit(formData) {
if(formData.valid) {
console.log(formData.value);
this.af.auth.createUserWithEmailAndPassword(
formData.value.email,
formData.value.password
).then(
(success) => {
console.log(success);
success.updateProfile({
displayName: "Example User",
photoURL: "https://example.com/jane-q-user/profile.jpg"
}).catch(
(err) => {
this.error = err;
});
this.router.navigate(['/login'])
}).catch(
(err) => {
this.error = err;
})
}
}
Note that in my example the displayName is set to "Example User", in the real app you just add the parameter as in my case it should be -> displayName:formData.value.name