I am very new to Flutter Web and have been pulling my hair out over this one. This is exactly how I authenticate my users on mobile but for some reason when I use hot reload on flutter web the user authentication is lost and returns null and shows the CircularProgressIndicator(CPI).
Why is it doing this? If null is called then surely it should show the LoginPage() - which is does when the page is initially loaded - but as soon as I hot reload it returns null and the CPI.
Further - I added a print statement to help me keep track of the user calls - and noticed each time I navigate to a new page the user id is printed and wondering why this is?
What I would like to achieve:
Landing page directs user to login page if snapshot.data == null
Landing page directs user to home page if snapshot.data != null
Return a circularprogressindicator if ConnectionState.waiting
Fix the error as described above
class Landing extends StatelessWidget {
#override
Widget build(BuildContext context) {
final auth = Provider.of<UserRepository>(context, listen: false);
return StreamBuilder(
stream: auth.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
User user = snapshot.data;
print(user);
return user == null ? LoginPage() : HomePage();
} else
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
},
);
}
}
I was running the program via F5 rather than flutter run -d chrome
Running the program via F5 allows one to press the hot reload button on VScode but importantly hot reload is currently not available on Flutter Web.
I believe, in part, this has answered my question.
You are passing a stream into a Stateless widget.
Change Stateless widget to stateful widget this will solve your problem.
Related
Using Google provider with this method so I can detect logins/outs and redirect user from login page to different page:
FirebaseAuth.instance.authStateChanges().listen()
For now I have it set so user state is preserved until browser closed:
FirebaseAuth.instance.setPersistence(Persistence.LOCAL);
Unfortunately, Flutter hot reloads don't trigger authstatechanges() when i use Google provider from home page (home page is the login page). A manual browser refresh with F5 does seem to trigger the method (assuming it forces it to read the cookie) and it redirects as it should.
Is there a way to trigger authstatechange() manually as the google provider login screen doesnt ask for credentials on hot reload but it doenst trigger firebase authstatechanges().
Use startWith() or ConcatStream() from rxdart to force the stream start
with latest auth state:
import 'package:rxdart/rxdart.dart';
class SomeClass {
void init() {
FirebaseAuth.instance
.authStateChanges()
.startWith(FirebaseAuth.instance.currentUser)
.listen(...);
}
}
or with ConcatStream:
import 'package:rxdart/rxdart.dart';
class SomeClass {
void init() {
final authStateStream = ConcatStream([
FirebaseAuth.instance.currentUser,
FirebaseAuth.instance.authStateChanges(),
]);
authStateStream.listen(...);
}
}
In my application, I'm using routes to navigate between pages.
Here's how I implemented route:
In main.dart file, I've defined routes like this:
bool session = await isValidSession(); //queries database and validate session
if (session) {
_defaultHome = new HomePage();
}
final routes = {
'/login': (BuildContext context) => new LoginPage(),
'/home': (BuildContext context) => new HomePage(),
//several other pages
'/logout' : (BuildContext context) => new LogoutPage(),
};
I've passed that route as argument to runApp method:
runApp(new MaterialApp(
title: 'My Application',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: _defaultHome,
routes: routes,
));
The pages are navigatable through drawer menu. When user will Tap on a menu, this code executes and take customer to respective page.
Navigator.of(context).pushNamed("/PAGE_NAME");
Problematic Scenario:
When user open app, fill up form and try to login
A function validates credentials and login user
Upon successful login, I've this code for navigating to Home page.
Navigator.of(context).popAndPushNamed("/home");
According to official documentation of popAndPushNamed method,
Pop the current route off the navigator that most tightly encloses the given context and push a named route in its place.
So according to my understanding, popAndPushNamed is supposed to remove login page from context and will navigate to home page.
It doesn't work as expected, it navigates to home page but doesn't remove login page from context, because pressing back button, takes back to login page (even the form is in filled state).
Alternatively, if I pop current context and push to new page then it works as expected.
Navigator.pop(context);
Navigator.of(context).pushNamed("/home");
But there's still an issue with this approach, when I Tap on back button from home page. The screen goes blank, instead of quitting the app.
Questions:
How do I properly remove login page from context? (if user press back button then app quits)
When user logout, how do I remove all pages from context and navigate to login page? (already tried but it doesn't work in my case)
Navigator.of(context).popUntil(ModalRoute.withName('/login'));
you can try this for logout :
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (BuildContext context) => LoginPage()),
ModalRoute.withName('/'));
I basically want to redirect to the previous url when a user has successfully logged in.
I redirect to the login page with the previous url such as /login?redirect=/page1/page2.
And I want when a user authenticates to be redirected back to that url.
I am using the auth-module here: https://auth.nuxtjs.org/
How I login the user.
methods: {
async submit() {
await this.$auth.loginWith('local', {
data: this.form
})
}
}
The only thing that I could found in the docs is this: https://auth.nuxtjs.org/getting-started/options#redirect
which however only redirects to a specific page instead of the previous page in the query.
Any ideas on how to achieve this?
You Can Do This
this.$router.back()
And its go back to the last route.
Programmatic Navigation | Vue Router
https://router.vuejs.org/guide/essentials/navigation.html
Thanks.
There is a fairly detailed discussion in github about Nuxt having an issue with a redirect when you are hitting a protected page directly. The redirect goes to the default page redirect rather than the previously hit page. The correct behavior should be to store the redirect and then proceed to it after authentication (login) with correct credentials.
3 days ago (Apr 14, 2019), MathiasCiarlo submitted a PR on the auth-module repo to fix this. The base reason why the redirect was "lost" has to do with the state of the redirect value not being allowed to be set as a cookie in SSR mode. His code impacts the storage.js file, in particular the setCookie() method. I've included that changed method here just for reference.
setCookie (key, value, options = {}) {
if (!this.options.cookie) {
return
}
const _key = this.options.cookie.prefix + key
const _options = Object.assign({}, this.options.cookie.options, options)
if (isUnset(value)) {
Cookies.remove(_key, _options)
} else {
// Support server set cookies
if (process.server) {
this.ctx.res.setHeader('Set-Cookie', [_key + '=' + value])
} else {
Cookies.set(_key, value, _options)
}
}
return value
}
I've personally just altered my npm myself, but you could probably fork the repo and use that forked npm for the time being. Or you could wait until the PR is merged into the mainline of the auth-module repo.
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
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.