How to stop page content load until authenticated using Firebase and Polymer - authentication

I am starting with Polymer and Firebase and have implemented the Google OAuth authentication.
I have notice the page loads before authentication and if you click back you can get to the page without authorization, albeit that you are not able to use the firebase api and therefore the page is not usable.
My issue is that I do not want my javascript loaded until authenticated.
How could this be done.
Many thanks

It depends if your using firebase or their polymer wrapper, polymerfire.
Create a document for all the imports that you want to be conditionally loaded
// user-scripts-lazy.html
<link rel="import" href="user-script-one.html">
<script src="script.js"></script>
// etc
Using Polymerfire
In the element that hosts <firebase-auth> create a observer and you'll expose some variables from firebase-auth.
<firebase-auth
user="{{user}}"
status-known="{{statusKnown}}"></firebase-auth>
In the observer, watch the user element and the status known
statusKnown: When true, login status can be determined by checking user property
user: The currently-authenticated user with user-related metadata. See the firebase.User documentation for the spec.
observers:[
'_userStateKnown(user, statusKnown)'
]
_userStateKnown: function(user, status) {
if(status && user) {
// The status is known and the user has logged in
// so load the files here - using the lazy load method
var resolvedPageUrl = this.resolveUrl('user-scripts-lazy.html.html');
this.importHref(resolvedPageUrl, null, this.onError, true);
}
}
To get the state without using polymerfire you can use onAuthStateChange
properties: {
user: {
type: Object,
value: null // important to initialise to null
}
}
..
ready: function() {
firebase.auth().onAuthStateChagned(function(user) {
if(user)
this.set('user', user); // when a user is logged in set their firebase user variable to ser
else
this.set('user', false); // when no user is logged in set user to false
}.bind(this)); // bind the Polymer scope to the onAuthStateChanged function
}
// set an observer in the element
observers: [
'_userChanged(user)'
],
_userChanged: function(user) {
if(user === null) {
// authStatus is false, the authStateChagned function hasn't returned yet. Do nothing
return;
}
if(user) {
// user has been signed in
// lazy load the same as before
} else {
// no user is signed in
}
}
I haven't tested the code while writing it here, but i've implemented the same thing various times.

There are a couple of options.
Put content you don't want loaded behind a dom-if template with "[[user]]" as its driver. This could include your firebase element, so the database isn't even considered until after log on.
Put a modal dialog box up if the user is not logged on. I do this with a custom session element . Whilst the overlay is showing then the rest of the page is unresponsive to anything.

If it is simply an aesthetic issue of removing the non-logged-in page from view, could you either hide the page (or display some kind of overlay) while the user isn't authenticated?
I currently have this in an current project for some elements: hidden$="{{!user}}"

I have identified the solution for my purpose ...
Add storage role based authorization (see is there a way to authenticate user role in firebase storage rules?)
This does have a limitation currently of hard coded uid's
In the page, request storage resource and if successful include it in the dom (i.e. add script element with src pointing to storage url)
Call javascript as normal

Related

Vue,js used with Supabase - can't update signIn button after logging in with Oauth

async handleSignInSignOutButtonClick() {
if (!this.isSignedIn) {
supabase.auth.signIn({ provider: "google" });
this.$store.commit("signIn", supabase.auth.session());
window.location.reload();
return;
}
await this.$store.commit("signOut");
supabase.auth.signOut();
window.location.reload();
},
The above function is triggered by a sign-in button, which is supposed to become a sign-out button and the icon of the user after logging in.
When The function fires, supabase redirects me to Google OAuth consent screen. However, after logging in and redirecting back to my app, the sign-in button stays there until I manually refresh the page.
What is wrong with my code...
There are a couple of things going on that you need to be aware of. For starters you are reloading your page when you don't need to in the handleSignInSignOutButtonClick() function.
When the authentication process begins, your app will be redirected to Google OAuth consent screen as you have discovered. Once the authentication is complete, you will be redirected back to your app and the reload occurs automatically.
The second point is that you can make use of the supabase.auth.onAuthStateChange() event to help you. My suggestion would be to listen for this event when you create your supabase client so it listens for the duration of your app instance. During that event handling, you can assign the user to the store (or anywhere you want to save the user data) based upon the state change. Your app can be reactive to state changes.
In your supabase client setup code:
const supabaseUrl = process.env.SUPABASE_URL // your supabaseUrl
const supabaseAnonKey = process.env.SUPABASE_ANON_KEY // your supabaseKey
const supabase = createClient(supabaseUrl, supabaseAnonKey)
/**
* Set up the authentication state change listener
*
* #param {string} event The event indicates what state changed (SIGN_IN, SIGN_OUT etc)
* #param {object} session The session contains the current user session data or null if there is no user
*/
supabase.auth.onAuthStateChange((event, session) => {
const user = session?.user || null;
// Save your user to your desired location
$store.commit("user", user);
});
Now you have your user data being saved whenever the user logs in and a null being set for the user data when the user logs out. Plus any page refreshes are handled by the change state event listener or any other instance that might change the user state. For example, you could have other login or logout buttons and the single listener would pick them up.
Next is to deal with the actual process of logging in or out. In your component Vue file (from your example):
async handleSignInSignOutButtonClick() {
if ($store.state.user === null) {
await supabase.auth.signIn(
{ provider: "google" },
{ redirectTo: "where_to_go_on_login" }))
} else {
await supabase.auth.signOut()
$router.push("your_logged_out_page")
}
}
Finally for your button change state to indicate logged in or logged out, you can simply observe the store user state.
<button v-if="user">Sign Out</button>
<button v-else>Sign In</button>
This way your button will update whenever the user state changes. The user state changes whenever a user logs in or out, and your code is much more compact and readable.
Once final observation that you may already be doing anyway. I would recommend that you put all of your authentication code into a single file and expose the log in and log out functions for your app use as an export to use in component files. This way everything to do with login and logout is handled in a single location and this code is abstracted away from the component file. If you ever wanted to switch from Supabase you could easily update one or two files and everything else would just keep working.

Ionic 3 app - allow navigation to page if user logged (check on one place, rather on every component)

How can I add check whether user is logged or not and in accordance with that, allow navigation to desired page or not? It wouldn't be good practice to add :
ionViewCanEnter() {
return this.auth.isAuthenticated();
}
check at the top of every component...
I recommend using an authentication token for your user login. This will allow you to locally store as a variable or local storage and you can implement in your service or provider to be used throughout the app. If you're uncertain with how they work there are plenty of resources online, but ultimately it comes down to your back-end server. Here's an example:Auth Token Example
Also, I would recommend you use *ngIf statement blocks in your html pages where the buttons navigate to the pages themselves and throw an alert if the user tries clicking on the button to navigate.
I have some sample code that can help guide you with this as well.
LoginPage.ts
// API POST authentication
this.API.validateUser(form.value).then((result) =>{
form.reset();//clears values of the form after data is saved to array
this.res = JSON.parse(result.toString());//converts result to array
//console.log(this.res);
if(this.res.token!=""){//sets authtoken to local storage
this.storage.set('authToken',this.res.token)
}
//console.log(localStorage);
if(this.res.status == true){
setTimeout(() => {
LoginPage.initialLogin = true;
this.navCtrl.push(MenuPage);
loading.dismiss();
}, 1000);
}
MenuPage.ts
// MenuPage.ts
/* calls local storage once user hits menupage*/
if(LoginPage.initialLogin==true){
//console.log('Initial Login is:',LoginPage.initialLogin);
this.storage.get('authToken').then((data)=>{//grabs local storage auth token
if(data!=null){
//console.log('GET request happened');
this.loggedIn = true;//User is logged in
this.reap.grabAPIData(data);//calls service to grab API data on initial login
}
});
}
else{
this.reap.getLocalStorage();
//console.log('Initial Login is:',LoginPage.initialLogin);
}
MenuPage.html
This is where you can use your value to determine what the user can see or not see. The button can be hidden or you can throw an alert in the .ts file that lets user know they aren't logged in.
<ion-item *ngIf="loggedIn" no-lines>
<button class="menuButton" ion-button large block (tap)="toNexPage()" >
Next page</button>
</ion-item>

How to authenticate users in FirefoxOS using BrowserID / Persona?

I am trying to write an FirefoxOS app for my portal which uses Mozilla Persona for authentication. How I should proceed if I want to achieve:
Allow users of my app to signup to my portal using Persona
Allow users of my app to login to my portal within the FirefoxOS app and perform some actions with the API
Depends if users is logged or not - giving access to different actions.
I have found this post with info that its integrated already: http://identity.mozilla.com/post/47114516102/persona-on-firefox-os-phones but I can't find any real examples.
What type of application I need to create? webapp or privileged?
I am trying to implement it using regular tutorial: https://developer.mozilla.org/en/Persona/Quick_Setup
But with this code:
signinLink.onclick = function() { navigator.id.request(); };
I am getting only following error:
[17:25:18.089] Error: Permission denied to access object
One thing is to make sure you're calling watch() to setup callbacks before you call request().
For example, something like this in your page:
<script src="https://login.persona.org/include.js"></script>
<script>
window.addEventListener("DOMContentLoaded", function() {
navigator.id.watch({
// Provide a hint to Persona: who do you think is logged in?
loggedInUser: null,
// Called when persona provides you an identity assertion
// after a successful request(). You *must* post the assertion
// to your server for verification. Never verify assertions
// in client code. See Step 3 in this document:
// https://developer.mozilla.org/en/Persona/Quick_Setup
onlogin: function(assertion) {
// do something with assertion ...
// Note that Persona will also call this function automatically
// if a previously-signed-in user visits your page again.
},
onlogout: function() {
// handle logout ...
},
onready: function() {
// Your signal that Persona's state- and callback-management
// business is complete. Enable signin buttons etc.
}
});
// Set up click handlers for your buttons
document.getElementById("signin").addEventListener(
'click', function() {
navigator.id.request({
// optional callback to request so you can respond to
// a user canceling the sign-in flow
oncancel: function() { /* do something */ }
});
}
});
});
</script>
Here's an example that shows this in action:
https://people.mozilla.org/~jparsons/persona_example.html
However, on FirefoxOS, you should be aware that installed apps (not packaged or certified, but generic installed apps) are given a unique origin of the form app://{uuid}, with a different uuid for each device. This is unfortunately useless for sign-in purposes because there's no way for your server to know whether an app requesting sign-in is friend or foe. The way around this problem is to run your persona code in an invisible iframe hosted on your server. Thus the iframe will have the correct origin and your server will know it's your app. The iframe and the app can communicate via postMessage.
In the case of a packaged app (sometimes called a privileged app), your origin will be the origin declared in your webapp manifest. E.g., app://yourapp.yoursite.org. This gives you better assurance that the app is really yours, but the truly paranoid may still wish to deploy the iframe trick.
Hope this helps!
j

Show pages based on authentication roles in Durandal SPA, MVC web app?

what i'm trying to do is lock down certain pages in my durandal(knockoutjs/breezejs) SPA app based on a user's authenticated role (ie, if you're an admin, etc, you should see pages that others shouldn't).
ideally, server code could be used to either output the link or not based on role, but since durandal and SPA apps don't really work that way with server code, i'm at a loss.
i realize this question has been asked in other forms here (i.e. how to use .cshtml (server) pages with Durandal), and this could be an option if perhaps i could get it to work. i have yet to find a very complete example that works for me.
any ideas appreciated!
You can keep your routing information on the server and supply it to the application on initialization.
For example. In the activate method of your shell viewmodel
shell.js
define(['durandal/system', 'plugins/router', 'durandal/app', 'services/datacontext'],
function (system, router, app, datacontext) {
var routeInfo = ko.observable();
var shell = {
activate: activate,
router: router
};
return shell;
//#region Internal Methods
function activate() {
app.title = "Sample App";
return boot();
}
function boot() {
return datacontext.getRouteInformation(routeInfo).then(function() {
return router.map(routeInfo()).buildNavigationModel().mapUnknownRoutes('error', 'Error').activate();
});
}
//#endregion
});
my datacontext.getRouteInformation() gets a json array of route information based on the current users security context and populates the routeInfo observable. that observable is then passed to the map function to create the users valid routes etc.
I know this doesn't fully "lock down" the html files etc, but all of my controllers and actions have authorization attributes on them, so the data is protected.

Refresh MVC view after logging in using Angular

Working with the Breeze Angular SPA template found here, http://www.breezejs.com/samples/breezeangular-template, I'm trying to update a menu that changes after user authenticates.
My example is slightly different from the default template in that I've moved the Login and Register views into modal windows. When the modal closes after a successful login, the menu, which is in the MVC View (and not the Angular View) does not update as a complete page refresh does not occur.
In the SPA template, authentication is required before entering the SPA, then a hard redirect/refresh occurs and the SPA is loaded. In my case, you could be browsing views/pages in the SPA before authenticating.
MVC View Code Snippet (Views/Home/Index.cshtml)
...
<li>
#if (#User.Identity.IsAuthenticated)
{
User Logged In: #User.Identity.Name
}
else
{
User Logged In: Annon
}
</li></ul>
<div ng-app="app">
<div ng-view></div>
</div>
....
I have working the root redirect, after login, the page hard refreshes if json.redirect is set to '/'. However, if its set to the current page, i.e. '#/about', Angular handles the routing and therefore no hard refresh occurs, thus the menu is not updated.
Ajax Login Code Snippet (App/ajaxlogin.js)
... part of login/register function
if (json.success) {
window.location = json.redirect || location.href;
} else if (json.errors) {
displayErrors($form, json.errors);
}
...
Is this possible to do using my current setup? Or do I need to move the menu somewhere inside the SPA and use Angular to determine what menu to show? If the latter, direction in how to best do this? I'm new to both Angular and Breeze.
The TempHire sample in Breeze has a really good way of handling authentication for a SPA (in my opinion at least!) Granted this is using Durandal so you will need to adapt it to Angular, but they are both frameworks doing the same basic principles so good luck! -
Basically, the Controller action has an annotation [Authorize] on the action that the prepare method is calling on the entitymanagerprovider. If a 401 is returned (not authorized) the SPA takes the bootPublic path and only exposes a login route to the user. When the login is successful, the login method tells the window to reload everything, at which time the authorization passes, and the bootPrivate method is called -
shell.js (Durandal, but should be adaptable)
//#region Internal Methods
function activate() {
return entitymanagerprovider
.prepare()
.then(bootPrivate)
.fail(function (e) {
if (e.status === 401) {
return bootPublic();
} else {
shell.handleError(e);
return false;
}
});
}
function bootPrivate() {
router.mapNav('home');
router.mapNav('resourcemgt', 'viewmodels/resourcemgt', 'Resource Management');
//router.mapRoute('resourcemgt/:id', 'viewmodels/resourcemgt', 'Resource Management', false);
log('TempHire Loaded!', null, true);
return router.activate('home');
}
function bootPublic() {
router.mapNav('login');
return router.activate('login');
}
login.js -
function loginUser() {
if (!self.isValid()) return Q.resolve(false);
return account.loginUser(self.username(), self.password())
.then(function() {
window.location = '/';
return true;
})
.fail(self.handleError);
}
The account.loginUser function is basically just an ajax call that passes credentials to the account controller and returns a success or failure. On success you can see the callback is fired for window.location = '/' which does a full reload. On failure simply show an alert or something.