I'm working on an Angular web app that is using Firebase to authenticate the user when they login or register. I want to restrict going to the /dashboard url unless they're logged in. I tried following Firebase's docs, but I'm coming up with errors.
I think where I'm having problems was making my controller code work with the one provided. I kept getting the error "Unknown provider: AuthProvider <- Auth <- currentAuth", so I just took out their controller code for now.
Any help would be great!
Here's the doc link: https://www.firebase.com/docs/web/libraries/angular/guide/user-auth.html#section-routers
And my code:
ROUTER CONFIG
var app = angular.module('maggsLashes', ['ngRoute', 'ui.calendar', 'firebase']);
app.config(function($routeProvider) {
$routeProvider
.when('/dashboard', {
templateUrl: 'app/templates/dashboardTmpl.html',
controller: 'dashboardCtrl',
resolve: {
// controller will not be loaded until $requireAuth resolves
// Auth refers to our $firebaseAuth wrapper in the example above
"currentAuth": ["Auth", function(Auth) {
// $requireAuth returns a promise so the resolve waits for it to complete
// If the promise is rejected, it will throw a $stateChangeError (see above)
return Auth.$requireAuth();
}]
}
})
DASHBOARD CONTROLLER
app.controller('dashboardCtrl', function($scope, $firebaseAuth, $location) {
var ref = new Firebase("https://maggslashes.firebaseio.com/");
var auth = $firebaseAuth(ref);
// app.controller("AccountCtrl", ["currentAuth", function(currentAuth) {
// // currentAuth (provided by resolve) will contain the
// // authenticated user or null if not logged in
// }]);
// console.log(auth);
console.log("Matt is pretty much awesome");
ref.onAuth(function(authData) {
if (authData) {
console.log("User is authenticated w uid:", authData);
}
else {
console.log("client sucks");
}
});
$scope.logOut = function() {
$location.path('/');
$scope.apply();
ref.unauth();
console.log(authData.uid);
};
});
I can see what may be at least on issue. In your router, you refer to "Auth" but do not see your code for that service/factory, so it's possible you don't have that set up. Note that "Auth" is a custom factory.
not you need this somewhere in your code:
app.factory("Auth", ["$firebaseAuth",
function($firebaseAuth) {
var ref = new Firebase("https://docs-sandbox.firebaseio.com", "example3");
return $firebaseAuth(ref);
}
])
Also, can you provide the errors you're receiving?
Wayne
Related
I am using ui-router. I am trying to authenticate all pages except sign up page.
Here are important parts of code:
In app.js:
$transition.onStart({ to: function(state) {
return state.data != null && state.data.authRequired === true;
}},function(trans){
var AuthService = trans.injector().get('AuthService');
....
});
In routes.js:
$stateProvider.state('signup', {
url: '/signup',
templateUrl:'views/signeup.html',
controller: 'SigneUp',
data: {
authRequired: false
}
});
But I am not allowed to go to signup page unless I am authenticated.
You will need to have a service that does Authorization and stores state of current auth for any given user. From there, in each of your controllers, check for auth status, where required, allow access when not logged in; where not, make a stop gate.
eg:
angular.module('app', [])
.controller('ctrlname', ['$scope', '$location', 'myAuthenticationService', function($scope, $location, myAuthenticationService){
//userId and Password to be bound to partials via ng-model.
if (myAuthenticationService.authorizeUser(userId, password)){
// DO what you have to for an authorized user.
}
else{
//
$location.route('/unauthorized');
}
}]
.service('myAuthenticationService', ['$http', function($http){
var self = this;
//This is just for reference, might need more sophesticated authentication on server side anyways.
self.authorizeUser = function(userId, password){
return $http.get(`url/?userId=${userId}&password=${password}`)
.success(function(response){
//If user is validated,
return true;
})
.error(function(error){
return false;
})
}
return {
authorizeUser: function(userId, password){
return self.authorizeUser(userId, password);
}
}
}]
You could define your routes and add corresponsing controllers in routes.js.
I am trying to anonymously authenticate users using AngularFire. I want to authenticate a user only once (so, if the user has already been authenticated, a new uid won't be generated). When I use the code below, I get a previous_websocket_failure notification. I also get an error in the console that says TypeError: Cannot read property 'uid' of null. When the page is refreshed, everything works fine.
Any thoughts on what I'm doing wrong here?
app.factory('Ref', ['$window', 'fbURL', function($window, fbURL) {
'use strict';
return new Firebase(fbURL);
}]);
app.service('Auth', ['$q', '$firebaseAuth', 'Ref', function ($q, $firebaseAuth, Ref) {
var auth = $firebaseAuth(Ref);
var authData = Ref.getAuth();
console.log(authData);
if (authData) {
console.log('already logged in with ' + authData.uid);
} else {
auth.$authAnonymously({rememberMe: true}).then(function() {
console.log('authenticated');
}).catch(function(error) {
console.log('error');
});
}
}]);
app.factory('Projects', ['$firebaseArray', '$q', 'fbURL', 'Auth', 'Ref', function($firebaseArray, $q, fbURL, Auth, Ref) {
var authData = Ref.getAuth();
var ref = new Firebase(fbURL + '/projects/' + authData.uid);
console.log('authData.uid: ' + authData.uid);
return $firebaseArray(ref);
}]);
In your Projects factory, you have assumed authData will not be null. There are no guarantees here, since your Projects factory is initialized as soon as you inject it into another provider. I also noticed that your Auth service doesn't actually return anything. This probably means that the caller has to know the internal workings and leads to quite a bit of coupling. A more SOLID structure would probably be as follows:
app.factory('Projects', function(Ref, $firebaseArray) {
// return a function which can be invoked once
// auth is resolved
return function(uid) {
return $firebaseArray(Ref.child('projects').child(uid));
}
});
app.factory('Auth', function(Ref, $firebaseAuth) {
return $firebaseAuth(Ref);
});
app.controller('Example', function($scope, Auth, Projects) {
if( Auth.$getAuth() === null ) {
auth.$authAnonymously({rememberMe: true}).then(init)
.catch(function(error) {
console.log('error');
});
}
else {
init(Auth.$getAuth());
}
function init(authData) {
// when auth resolves, add projects to the scope
$scope.projects = Projects(authData.uid);
}
});
Note that dealing with auth in your controllers and services should generally be discouraged and dealing with this at the router level is a more elegant solution. I'd highly recommend investing in this approach. Check out angularFire-seed for some example code.
I have a angularjs ui-router situation where:
User must be authorized before hitting any page
If user is authorized and has no route, redirect to their homepage
If user is authorized and has a route, redirect to route
If user is authorized and has no route and no homepage, navigate to default page
If user is not authorized and has route, redirect to login page and upon authorization redirect to that route
Its a tricky situation and I can't seem to nail it just right. My current code does work but... it has to shows the 'login' page for a split second before navigating. This happens because I have to kick off the $stateChangeStart somehow.
var app = angular.module('myapp', ['ui.router']);
// handle default states based on authentication,
// default properties set in user profile, or
// or just redirect to 'apps' page
var authd = false,
defaultDashboard = undefined,
defaultFn = function($injector){
// required to get location since loaded before app
var $location = $injector.get('$location');
// if the user has a default dashboard, navigate to that
if(defaultDashboard){
$location.path('workspace/' + defaultDashboard);
} else if(authd) {
// if the user is auth'd but doesn't have url
$location.path('home');
} else {
// if we aren't auth'd yet
$location.path('login');
}
};
app.config(function ($urlRouterProvider, $locationProvider, $stateProvider) {
$locationProvider.html5Mode(true);
app.stateProvider = $stateProvider;
$urlRouterProvider.otherwise(function($injector){
defaultFn($injector);
});
});
app.run(function ($rootScope, $q, $location, $state, $stateParams, $injector, security) {
var deregister = $rootScope.$on("$stateChangeStart", function () {
// authorize is a AJAX request to pass session token and return profile for user
security.authorize().success(function(d){
// set some local flags for defaultFn
authd = true;
defaultDashboard = d.defaultDashboard;
// de-register the start event after login to prevent further calls
deregister();
// switch to default view after login
if($location.$$url === "/login" ||
$location.$$url === "/"){
defaultFn($injector);
}
}).error(function(){
$location.path('login');
});
});
});
I'm using a inceptor to handle 401s like:
var module = angular.module('security.interceptor', []);
// This http interceptor listens for authentication failures
module.factory('securityInterceptor', function($injector, $location) {
return function(promise) {
// Intercept failed requests
return promise.then(null, function(originalResponse) {
if(originalResponse.status === 401) {
$location.path('/login');
}
return promise;
});
};
});
// We have to add the interceptor to the queue as a string because the
// interceptor depends upon service instances that are not available in the config block.
module.config(function($httpProvider) {
$httpProvider.defaults.withCredentials = true;
$httpProvider.responseInterceptors.push('securityInterceptor');
});
anyone had any similar cases and found a better solution?
Heres my solution I came up with:
app.config(function ($urlRouterProvider, $locationProvider, $stateProvider) {
$locationProvider.html5Mode(true);
// placeholder
$stateProvider.state('welcome', {
url: '/'
});
$urlRouterProvider.otherwise('404');
});
app.run(function ($rootScope, $q, $location, $state, $stateParams, security, $urlRouter) {
var deregister = $rootScope.$on("$stateChangeStart", function (event) {
// stop the change!
event.preventDefault();
security.authorize().success(function(d){
// if we don't have a previous url
if($location.$$url === "/" || $location.$$url === "/login"){
// If user has a preset home
if(d.defaultDashboard){
$location.path('workspace/' + d.defaultDashboard);
} else {
$location.path('welcome');
}
} else {
// if we do, then continue
$urlRouter.sync();
}
}).error(function(){
// redirect to home
$location.path('login');
});
// deregister the listener
deregister();
});
});
Essentially, creating a empty route for an empty route solved my problem. Interesting.
I'm new to AngularJS and just building an app to learn it. My app calls a REST API and right now I have the hostname hard coded in the app. I want to make this in app setting (and maybe later have somewhere to configure it). I thought I'd start with a constant. Should it go in my app.js like this? If so, I'm not sure of the syntax for adding it to the .config settings with $routeProvider there too
(function () {
// Define module and add dependencies inside []
var app = angular.module("haClient", ["ngRoute"]);
app.constant('hostname', 'http://192.192.192.192:8176');
app.config(function ($routeProvider) {
$routeProvider
// Register routes
// Main route
.when("/main", {
templateUrl: "main.html",
controller: "MainController"//,
//activeTab: 'home'
})
// Device List
.when("/devices", {
templateUrl: "devicelist.html",
controller: "DeviceListController"
})
// Device details (param is device name)
.when("/device/:devicename", {
templateUrl: "device.html",
controller: "DeviceController"
})
// Invalid URL's get sent back to main route
.otherwise({ redirectTo: "/main" });
}); // End App Config
}());
This is the module that needs to use it (called from controllers):
(function () {
var deviceCtrl = function ($http) {
var getDevices = function () {
return $http.get("http://192.192.192.192:8176/devices.json/")
.then(function (response) {
return response.data;
});
};
// get details and return a promise
var getDeviceDetails = function (deviceName) {
return $http.get("http://192.192.192.192:8176/devices/" + deviceName + ".json/")
.then(function (response) {
return response.data;
});
};
// Public API
return {
getDeviceDetails: getDeviceDetails,
getDevices: getDevices
};
};
var module = angular.module("haClient");
}());
Can someone enlighten em on the best way to set it and get it?
Thanks
I currently do this by using templates to build the root .html file in the backend. Eg, using doT templates in node.js, I put this below my other js includes:
<!-- load any templated constants -->
<script>
angular.module('mymod').constant('globals', {
api: '{{=it.api}}'
});
</script>
This way my backend can work out the logic of where the client needs to point to. In order to use the value in another service or controller, you simply inject the constant by name:
angular.module('somemod', []).factory('myservice', ['globals', function(globals){
// use globals.api or what ever you set here for example
}]);
The best place to do configuration is in a Provider and inside your config block.
Providers expose an API that allows you to configure your service before it's injected into your controllers and directives.
Suppose you have a service called myService that you want injected into your controller function like this:
app.controller('ctrl', function($scope, myService) { ...});
And myService is responsible for retrieving data through web API calls. Let's further assume that you would like to configure your service with the root URL htt://servername/, since all calls will share the same host name.
You can define your myServiceProvider like this:
app.provider('myService', function(){
var webapiurl;
this.setWebServiceUrl = function (url) {
webapiurl = url;
}
// the injector will call the $get function to create the singleton service that will be injected
this.$get = function( /*injectables*/) {
return {
getData: function() {... Use webapiurl ...}
}
}
});
Then in your config function, configure your provider:
app.config(function(myServiceProvider){
myServiceProvider.setWebServiceUrl('htt://servername');
});
Finally you can inject the service anywhere it can be injected:
app.controller('ctrl', function($scope, myService) {
$scope.data = myService.getData();
});
I couldn't get the supplied answers to work (no reason to think they wouldn't). here's what I did.
My main app.js (constants section)
(function () {
// Define module and add dependencies inside []
var app = angular.module("haClient", ["ngRoute"]);
//constants
app.constant('mySettings', {
baseURL: 'http://192.192.192.192:8176',
otherSetting: 'XYZ'
});
app.config(function ($routeProvider) {
$routeProvider
// Register routes
// Main route
.when("/main", {
templateUrl: "main.html",
controller: "MainController"//,
//activeTab: 'home'
})
// Device List
.when("/devices", {
templateUrl: "devicelist.html",
controller: "DeviceListController"
})
// Device details (param is device name)
.when("/device/:devicename", {
templateUrl: "device.html",
controller: "DeviceController"
})
// Invalid URL's get sent back to main route
.otherwise({ redirectTo: "/main" });
}); // End App Config
}());
In my service (added mySettings as a dependency and then just used mySettings.baseURL):
(function () {
var deviceCtrl = function ($http, $log, mySettings) {
$log.info("DeviceCtrl - baseURL: " + mySettings.baseURL);
// get device list and return a promise
var getDevices = function () {
return $http.get(mySettings.baseURL + "/devices.json")
.then(function (response) {
return response.data;
});
};
// get details and return a promise
var getDeviceDetails = function (deviceName) {
$log.info("DeviceCtrl - Getting device info for " + deviceName);
return $http.get(mySettings.baseURL + "/devices/" + deviceName + ".json")
.then(function (response) {
return response.data;
});
};
// Public API
return {
getDeviceDetails: getDeviceDetails,
getDevices: getDevices
};
};
var module = angular.module("haClient");
module.factory("deviceCtrl", deviceCtrl);
}());
I'm certainly no expert (as is clear from not being able to get supplied answers working), and I'm not sure (yet) if there are any drawbacks to this method. It allowed me to get on with my project and learn more of Angular, so I went with it.
Regards
Mark
I have used another module and injected it into app module like this
Create constant.js and include that in your index.html & add following code inside that
angular.module('constantsModule', []).constant('BASEURL', 'http://google.com');
Inside app.js, inject 'constantsModule' so that all constants inside it will be available for 'haClient'
angular.module('haClient', [
'constantsModule'
])
.config(function ($routeProvider) {
.when('/', {
templateUrl: 'views/landing.html',
controller: 'landingCtrl'
})
});
Inside landingCtrl, since its in scope of 'haClient', we can inject BASEURL constant from 'constantsModule'
angular.module('haClient').controller('landingCtrl', function ($scope, BASEURL) {
// just to show, how to access it inside controller
$scope.baseurl = BASEURL;
});
Angular JS Newbie: I get the gapi is not defined because sydney.userAuthed is called before the API is loaded. But I don't understand why the method sydney.auth is called when the controller is created. I get the error when the page loads, there is no user interaction.
calling auth api.js:22
ReferenceError: gapi is not defined
at Object.sydney.userAuthed (http://localhost:8888/js/api.js:36:8)
at Object.sydney.auth (http://localhost:8888/js/api.js:23:30)
at new LoginController (http://localhost:8888/js/controllers.js:8:24)
at invoke (http://localhost:8888/lib/angular/angular.js:2902:28)
at Object.instantiate (http://localhost:8888/lib/angular/angular.js:2914:23)
at http://localhost:8888/lib/angular/angular.js:4805:24
at updateView (http://localhost:8888/lib/angular/angular-ui-router.js:931:30)
at <error: illegal access>
at Object.Scope.$broadcast (http://localhost:8888/lib/angular/angular.js:8307:28)
at $state.transition.resolved.then.$state.transition (http://localhost:8888/lib/angular/angular-ui-router.js:747:20) angular.js:5754
OAuth2 API loaded api.js:13
I defined a state with ui-router as:
myapp.config(function($stateProvider, $routeProvider) {
$stateProvider.state('signin', {
url : "/", // root route
views : {
"signinView" : {
templateUrl : 'partials/signin.html',
controller: 'LoginController'
}
},
})
The controller is defined as:
function LoginController($scope, $state) {
$scope.auth = sydney.auth($state);
}
sydney.auth method is used to authenticate a user using Google APIs Client Library for JavaScript.
var sydney = sydney || {};
sydney.auth = function($state) {
console.log("calling auth");
sydney.signin(false, sydney.userAuthed($state));
}
sydney.signin = function(mode, callback) {
gapi.auth.authorize({client_id: sydney.CLIENT_ID,
scope: sydney.SCOPES, immediate: mode,
response_type: 'token id_token'},
callback);
console.log("signin called");
}
sydney.userAuthed = function($state) {
var request =
gapi.client.oauth2.userinfo.get().execute(function(resp) {
if (!resp.code) {
var token = gapi.auth.getToken();
token.access_token = token.id_token;
gapi.auth.setToken(token);
// User is signed in, call my Endpoint
gapi.client.realestate.owner.create().execute(function(resp) {
alert("Signed In");
$state.transitionTo("signedin");
});
}
});
}
EDIT
The correct answer is to define $scope.auth as a function:
function LoginController($scope, $state) {
$scope.auth = function() {
sydney.auth($state);
}
}
It seems what you're trying to do is make the sydney.auth function available from your scope. If so, you should change your LoginController to:
function LoginController($scope, $state) {
$scope.auth = sydney.auth;
}
What your code was doing was calling the sydney.auth function and setting $scope.auth to its return value, which is not what I think you intended.