Secure authentication structure in a React Webapp - javascript

I am currently learning React and worked through some courses but still haven't completely understood how to create a proper structure for a secure web app.
For the sign in, sign up flow I use the firebase SDK. Once logged in, a user gets redirected to a private route. Right now I only have 2 user roles. Guests and signed in Users. This enables me to create private routes by using an inbuild firebase function. This is the first problem as it is not scalable once I add different roles as it would force me to send a request to the backend to check what role the user is and thus which pages he can acces.
if (firebase.auth().currentUser === null) {
console.log("not logged in")
return (<Redirect
to={{
pathname: "/signin",
state: {
from: props.location
}
}}
/>);
}
So I thought that the easiest option would be to use Context, which did work. Once a user loggs in, the server sends a user object which the app refers to for the rest of the session. I followed a bunch of tutorials and they all had the same problem that when using chrome developer tools with the react features, you could just edit the state of the user and bypass the private routes etc.
Second Try:
<UserContext.Consumer>{(context)=>{
const {isLoggedIn} = context
return(
<Route
{...rest}
render={props => {
if (isLoggedIn) {
console.log("not logged in")
return (<Redirect
to={{
pathname: "/signin",
state: {
from: props.location
}
}}
/>);
I'd be grateful if somebody could point me in a direction as it seems like I am missing something important.
EDIT 1: Or is it simply that once you build the app, you can no longer access these states and it's considered safe?

when using chrome developer tools with the react features, you could just edit the state of the user and bypass the private routes
Your routes will never be truly private. They are part of the JavaScript bundle that gets downloaded and rendered by the browser, so they should never contain anything secret. Anyone could read this code if they really wanted to.
Consider this:
if (loggedIn) {
return <div>Secret data: ABC</div>;
}
The string "ABC" is contained in your app build, and is not really a secret anymore. The average user wouldn't know how to obtain it, but a developer probably would, for example by toggling some state in the developer console.
However, the data that comes from Firestore (or any another backend service) should be properly protected. Permission checks are done server-side before this data is sent to the browser. So, unless the user has the required permissions, the data will never be exposed to the wrong person, even if someone tampers with your client-side code in the developer console.
if (loggedIn) {
fetchDataFromBackend();
}
It doesn't matter if someone changes loggedIn to true so that fetchDataFromBackend() is called; the server will make sure the data isn't returned unless the user has the proper permission (e.g. is logged in). In the case of Firebase (Firestore), this protection is achieved with Security Rules.
And, by the way, the recommended way to get the current user with Firebase is to add a listener to the Auth object:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});
You could put this in a top-level component and share the user object with child components through a context. That way you don't have to call firebase.auth() all over the place. Here's a good starting point if you need some inspiration: https://usehooks.com/useAuth/

I think what you are doing on the frontend site is good, but you would also need logic in the backend to protect your routes. This means that an user may be able to circumvent your route protection via dev tools on the frontend, but your backend would only send error messages to him, as it recognizes that he has no allowance.
You could do this with Higher Order Functions like this one:
const authenticationWrapper = createResolver(
async ( models, session, SALT ) => {
try {
if (!session) {
throw new Error("No valid credentials!");
}
const { id } = verify(session.token, salt);
const valid = databasecall //
if (!valid) {
throw new Error("No valid user!");
}
return true
} catch (error) {
session.destroy((err) => {
if (err) {
console.error(err);
}
});
}
}
);
All private backend functions would be wrapped into and authentication of the user would be checked every time.
The principle to check in Front- and Backend is called dual authentication. You can read more about it here.

Related

How can I check or verify that a user is signed in with AWS Cognito Javascript?

I am creating a React web app where the user sign in/up and other authentication related processes are being handled by AWS Cognito and the accompanying Javascript SDK.
My app has some 'public' routes/pages that everybody, signed in or not, can view, such as /documentation/ and /sign-in/. There also exist various private routes which you can only see when you are logged in, such as /my-documents/.
At the moment, I have a working sign in page, where a user is signed in with code very similar to use case #4 (Cognito Docs).
My question now is: as soon as a user goes to /my-documents/, how do I check whether the user is signed in and actually has the rights to see this page?
I am not using AWS Amplify for the authentication in my app. I only use the NPM package 'amazon-cognito-identity-js'.
This is the code I currently use to check if the session is valid, in other words if the user is successfully signed in. This however, seems like a cumbersome way to check such a simple status.
const isAuthenticated = () => {
const cognitoUser = userPool.getCurrentUser();
let isSessionValid = false;
if (cognitoUser) {
cognitoUser.getSession((err: Error, result: CognitoUserSession) => {
if (!err) {
isSessionValid = result.isValid();
}
});
}
return isSessionValid;
};
isSessionValid is returned before the callback in getSession is executed.

Administrator Views for a Firebase Web Application: How To

My Firebase web app requires administrator access, i.e., the UI should show a few things only for admins (an 'administrator' section). I came up with the below as a means to authorize the UI to display the admin section for valid admins only. My question is, good or bad? Is this a sound means of authorizing? ...so many ways to do this. This particular way requires me to configure admins in the security rules (vs in a node/tree in a db/firestore)
My idea is that if the .get() fails due to unauthorized access, I tell my app logic the user is not an admin, if the .get() succeeds my logic shows the 'admin' sections. Of course, the 'sections' are just HTML skeletons/empty elements populated by the database so even if the end user hacks the JS/logic, no real data will be there - only the empty 'admin section' framework.
function isAdmin(){
return new Promise(function(resolve, reject){
var docRef = firebase.firestore().collection("authorize").doc("admin");
docRef.get().then(function(result) {
if (result) {
resolve (true);
}
}).catch(function(error) {
resolve (false);
});
});
}
The firestore rule specifies the 'admins' by UID.
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid == "9mB3UxxxxxxxxxxxxxxxxxxCk1";
}
}
}
You're storing the role of each user in the database, and then looking it up in the client to update its UI. This used to be the idiomatic way for a long time on realtime database, and it still works on Firestore.
The only thing I'd change is to have the rules also read from /authorize/admin, instead of hard-coding the UID in them. That way you only have the UID in one place, instead of having it in both the rules and the document.
But you may also want to consider an alternative: set a custom claim on your admin user, that you can then read in both the server-side security rules (to enforce authorized access) and the front-end (to optimize the UI).
To set a custom claim you use the Firebase Admin SDK. You can do this on a custom server, in Cloud Functions, but in your scenario it may be simpler to just run it from your development machine.
Detailed How To: Firebase has what's called Custom Claims for this functionality as detailed in their Control Access with Custom Claims and Security Rules. Basically, you stand up a separate node server, install the Firebase AdminSDK:
npm install firebase-admin --save
Generate/Download a Private Key from the Service Accounts tab in the Firebase Console and put that on your node server. Then simply create a bare bones node app to assign Custom Claims against each UID (user) that you wish. Something like below worked for me:
var admin = require('firebase-admin');
var serviceAccount = require("./the-key-you-generated-and-downloaded.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://xxxxxxxxxxxxxxxxxxxx.firebaseio.com"
});
admin.auth().setCustomUserClaims("whatever-uid-you-want-to-assign-claim-to", {admin: true}).then(() => {
console.log("Custom Claim Added to UID. You can stop this app now.");
});
That's it. You can now verify if the custom claim is applied by logging out of your app (if you were previously logged in) and logging back in after you update your web app's .onAuthStateChanged method:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
firebase.auth().currentUser.getIdToken()
.then((idToken) => {
// Parse the ID token.
const payload = JSON.parse(window.atob(idToken.split('.')[1]));
// Confirm the user is an Admin.
if (!!payload['admin']) {
//showAdminUI();
console.log("we ARE an admin");
}
else {
console.log("we ARE NOT an admin");
}
})
.catch((error) => {
console.log(error);
});
}
else {
//USER IS NOT SIGNED IN
}
});

In Meteor, how/where do I write code that triggers when an authorized user has logged in?

New to Meteor, I'm using the alanning:roles package to handle roles.
I've managed to be able to only publish a collection when a user is logged in, when the page is first refreshed.
Meteor.publish('col', function(){
if (Roles.userIsInRole(this.userId, 'admin')) {
console.log('authed');
return Sessions.find({});
} else {
console.log('not auth');
// user unauthorized
this.stop();
return;
}
});
Logging out kills access to the collection (I'm using mongol to see). Logging back in after logging out, or logging in from a logged out state when the page is first loaded, will not give me access.
The webapp I'm trying to build is something like an ticketing system. I'm trying to be secure, so no unnecessary publishing if the user is not authorized.
What I'm trying to do is, get ticket information submitted from users from a collection, and display it on the client screen (as long as the client is authorized first). Maybe a better way to handle this is to force a refresh (how do I do that?) after a user change so unauthorized users are "kicked" out? And render all relevant data from the private collection right after the user is authorized?
I actually managed to get what I want for now with helpers...
In my ./client/Subs.js file:
Meteor.subscribe('col');
Template.NewTicket.helpers({ // match the template name
// move this into method later, not secure because on client??
isAdmin() {
console.log('is admin.');
console.log(Meteor.userId());
Meteor.subscribe('col');
return Roles.userIsInRole(Meteor.userId(), 'admin');
},
});
and then somewhere in my template file ./client/partials/NewTicket.html:
<template name="NewTicket">
{{#if isAdmin}}
{{/if}}
</template>
to trigger the check? I'm 99% sure theres a better way.

How to handle user information using firebase simple login for facebook

I am building a webpage using AngularJS and Firebase. I want to use facebook login to connect information on the webpage with the user. Firebase has a version of simple login which I guess is supposed to simplify the login process.
My problem is that I want to access information about the logged in user in a lot of places on my webpage but I can't find a good way to do it.
This is how I started out:
var userInfo = null;
var ref = new Firebase('https://<yourfirebase>.firebaseIO.com/');
var auth = new FirebaseSimpleLogin(ref, function(error, user) {
if(error)
alert("You are not logged in");
else if(user)
{
//store userinfo in variable
userInfo = user;
}
else
//user logged out
});
//do something with userInfo
alert(userInfo.name);
My first thought was to run this at the top of my page and then use the info about the user. The problem is that the code using userInfo (as in e.g. the alert) will always run before the userInfo variable has been filled and userInfo will return undefined/null.
I then proceeded to always create a new firebasesimplelogin object when i want to retrieve user data. Which of course isn't very good. Especially since every created FirebaseSimpleLogin object will be called again whenever another is called or a user logs out, for example.
So my question is, how do I use FirebaseSimpleLogin to handle and use my user information in the best way?
I would have liked some function to getUserInfo() or check isLoggedIn() for example. How do you do this properly?
You can take a look at this example for thinkster. It's based on using simple login with userid/password. http://www.thinkster.io/angularjs/4DYrJRxTyT/7-creating-your-own-user-data-using-firebase.
You can create a function like getLoggedinUser that runs in $rootScope that will allow you to find the user throughout the application.
UPDATE:
Around the middle of October 2014, firebase made some big changes. This method might still work, but it's better to take advantage of the newer version of firebase, specifically getauth and onauth. These methods will allow you to do the same thing without running on the rootScope. https://www.firebase.com/docs/web/guide/user-auth.html#section-login
Please make a constant to use it everywhere in your App like that
.constant('facebookUrl', 'https://rjrestaurantapp.firebaseio.com');
Then in the controller inject this constant "facebookUrl & "$state" as shown below...
.controller('loginCtrl', function($scope,facebookUrl,$state){
and then you only need to give name of the state where you want to redirect after facebook authentication..
var ref = new Firebase(facebookUrl);
$scope.fbLogin = function () {
ref.authWithOAuthPopup("facebook", function(error, authData) {
if (error) {
console.log("Login Failed!", error);
} else {
console.log("Authenticated successfully with payload:", authData);
$state.go('restaurant');
}
})
}})
You can see the information in authData object after successfull authentication using facebook ....
please read this doc carefully https://www.firebase.com/docs/web/guide/login/facebook.html
The above is the example of simple login using firebase and for retrieving data for each logged in user, you have to store user information at the time of signin as you know that firebase makes every child with a unique ID .. then you only need to use the offline features of firebase that will find which user is offline and according to that remove the node with the same ID which one is offline, you can find examples in the MANAGING PRESENCE section of the below link ..
https://www.firebase.com/docs/web/guide/offline-capabilities.html

Firebase & Backbone: Application Authentication

Currently I'm building an application using firebase and backbone.marionette and I'm trying to implement secure sessions. Previously, I was able to simply bypass the login page by typing in a specific route in the URL bar, but to fix this I added an initializer to the app to check if a user is logged in or not, like so:
#addInitializer((options) =>
# Instantiate firebase
#firebase = new Firebase("https://*******.firebaseIO.com/")
#authClient = new FirebaseAuthClient #firebase,
(error, user) =>
if (error)
console.log(error)
else if (user)
console.log('User ID: ' + user.id + ', Provider: ' + user.provider)
#logged = true
#trigger('logged_in')
#router.navigate('home', {trigger: true})
else
#logged = false
#trigger('logged_out')
#router.navigate('login', {trigger: true})
)
And now before I render a page in routes.coffee I check if #logged is true or not.
But I feel like this is sketchy security at best. Couldn't someone just fire up the console and set the flag to true themselves?
Does anyone know the proper way to do sessions with backbone and firebase?
There's fundamentally no way to guarantee security on the client side. A smart hacker can always get around any restrictions you place on the GUI (such as setting #logged to true).
Instead, you need to set up security rules on the Firebase side so that non-authenticated users can't change data they're not supposed. This way, even if a hacker messes with your GUI they can't actually access or change anything they're not supposed to.
There's an overview of Firebase auth and security rules here:
https://www.firebase.com/docs/security-quickstart.html

Categories