Firebase & Backbone: Application Authentication - javascript

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

Related

Secure authentication structure in a React Webapp

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.

Error: AADSTS500011: The resource principal named "URL" was not found in the tenant

I am trying to add an app to our SharePoint Online site using the template from https://learn.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/build-a-hello-world-web-part and we get the error below when we deploy to SharePoint and add the app/Web part to a test SharePoint site. We are using TypeScript as the template uses.
Has anyone else encountered this issue or know where to look for the issue?
Found [object Object]Driver Display External Error: Error: AADSTS500011: The resource principal named https://driverdisplayexternal.azurewebsites.net was not found in the tenant named 7018324c-9efd-4880-809d-b2e6bb1606b6. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant. Trace ID: 358b22eb-cd2c-4091-b592-5a57cbc21d00 Correlation ID: ec96d656-1a36-42e2-a2b9-3ff78efc1e2e Timestamp: 2019-10-01 16:26:06Z
We have added a call to our own client as shown below. We are not sure why the resource principal was not found. The Tenant ID's match and things seem to be set up properly for authentication.
HelloWorldWebPart.ts
...
this.context.aadHttpClientFactory
.getClient('https://driverdisplayexternal.azurewebsites.net')
.then((client: AadHttpClient): void => {
client
.get('https://driverdisplayexternal.azurewebsites.net/api/values', AadHttpClient.configurations.v1)
.then((response: HttpClientResponse): Promise < Order[] > => {
this.domElement.innerHTML += 'Received a response from Driver Display External: ' + response;
return response.json();
})
.catch(error => {
this.domElement.innerHTML += 'Driver Display External Error: ' + error;
console.error(error);
});
});
...
package-solution.json
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "helloworld-webpart-client-side-solution",
"id": "**ID**",
"version": "4.1.0.0",
"includeClientSideAssets": true,
"isDomainIsolated": false,
"webApiPermissionRequests": [
{
"resource": "DriverDisplayExternal",
"scope": "User.Read.All"
}
]
},
"paths": {
"zippedPackage": "solution/helloworld-webpart.sppkg"
}
}
Any help or direction to where the issue may be would be very appreciated. Thanks in advance!
Never used this API, but if I had to guess you need to change the value here:
.getClient('https://driverdisplayexternal.azurewebsites.net')
You can use either the client id / application id, or the application ID URI.
Sometimes this problem can occurr when you set a wrong name for the scope you are requesting access for or another configuration parameter.
I suggest to check carefully the scopes name, or maybe directly use the "copy" button from the Azure portal.
In my case it was a simple typo on a scope name.
Not sure if you figured the answer or not. When you used SPFx to request your own custom web api end point. there are couple steps:
request the permission so that you can go to SPO admin to approve the permission you request. for this case, the webApiPermissionRequests->resources needs to your AAD Application's Service Principal DisplayName. once you had AAD App create, you can run Get-AzureADServicePrincipal to get all your ServicePrincipal.
once you request the permission, from your code, you need to call AadHttpClient.getClient() to get aadHttpClient object based on the api resourceEndpoint you want, for this case, you need to pass your web api's Application ID URI which can be found from your AAD App's manifest->"identifierUris". General speaking, this should be something like api://[clientid] format. but you can change it to any unique value.
I hope it helps.
In my case i had to use the App Id when i was consuming a multi tenant API.
In my case, TenantId and ClientId were both ok.
They can be found in AAD. TenantId is right there on landing page:
and then on the same page click Applications then tab All Applications find your application there should be ClientId check if they match.
If that is still not enough, click on the application and find roles
For me, it was roles that were missing after adding those wheels started rolling again:

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.

Session cookies not working in Electron

I'm looking at implementing a login system in an Electron[0] application which I'm building but getting stuck on the part of handling the session. Basically I want to store the users session so it is persisted between application restarts (if "Remember me" is enabled).
I have to make use of an existing back-end which works with cookie authentication and I'm not able to change anything there.
From the Electron documentation on the Session object[1] I gathered that I should be using a partition like f.e. persist:someName in order to have a persistent storage, but this is not persisted between application restarts as it seems.
The way I currently set the cookie is as follows:
// main-process/login.js
const session = require('electron').session;
const currentSession = session.fromPartition('persist:someName').cookies;
currentSession.set({
name: 'myCookie',
url: 'https://www.example.com',
value: 'loggedin=1',
expirationDate: 1531036000
}, function(error) {
console.log('Cookie set');
if (error) {
console.dir(error);
}
});
After running this, I see the Cookie set output, but when restarting the app and running the following code:
// main.js
const session = require('electron').session;
const currentSession = session.fromPartition('persist:someName').cookies;
currentSession.get({}, function(error, cookies) {
console.dir(cookies);
if (error) {
console.dir(error);
}
});
The output returned is [].
Any pointers as to what I'm doing wrong or need to do differently would be highly appreciated!
[0] http://electron.atom.io
[1] http://electron.atom.io/docs/api/session/
An alternative might be to take a look at electron-json-storage. Using this plugin, you can write JSON to a system file throughout the user experience and then recall that file on the application load to replace the user "state".

Categories