I'm experimenting with Angular, trying to setup a Angular frontend powered by a laravel backend (REST API). For authentication i want to use json web tokens. For Angular i'm using Satellizer (https://github.com/sahat/satellizer) and for Laravel JWT (https://github.com/tymondesigns/jwt-auth).
At the moment, i already can login with AngularJS using correct credentials stored in Laravel. The user info and token is stored in localStorage.
I would like to implement some kind of Angular service that can check if the user is authenticated in order to protect states form ui-router. I already tried a few tuts but i'm unable te get it working. Can someone point me in the right direction? Would be awesome!
loginController (Angular)
.controller('loginCtrl', [
'$scope',
'$rootScope',
'utils',
'$auth',
'$location',
'SweetAlert',
function ($scope,$rootScope,utils, $auth, $location, SweetAlert) {
$scope.login = function() {
var credentials = {
email: $scope.email,
password: $scope.password
};
$auth.login(credentials)
.then(function (response) {
var user = JSON.stringify(response.data.user);
localStorage.setItem('currentUser', user);
$location.path('/restricted.dashboard');
})
.catch(function (response) {
SweetAlert.swal("Inloggen mislukt.", "Controleer je email adres en wachtwood en probeer opnieuw.", "warning");
console.log("LOGIN NOT OK" + response);
});
};
app.states.js (ui-router, Angular)
.config([
'$stateProvider',
'$urlRouterProvider',
function ($stateProvider, $urlRouterProvider) {
// Use $urlRouterProvider to configure any redirects (when) and invalid urls (otherwise).
$urlRouterProvider
.when('/dashboard', '/')
.otherwise('/');
$stateProvider
.state("error.404", {
url: "/404",
templateUrl: 'app/componentsOld/pages/error_404View.html'
})
.state("error.500", {
url: "/500",
templateUrl: 'app/componentsOld/pages/error_500View.html'
})
// -- LOGIN PAGE --
.state("login", {
url: "/login",
templateUrl: 'app/components/auth/loginView.html',
controller: 'loginCtrl',
resolve: {
deps: ['$ocLazyLoad', function ($ocLazyLoad) {
return $ocLazyLoad.load([
'lazy_uikit',
'lazy_iCheck',
'app/components/auth/loginController.js',
'sweetAlert'
]);
}]
}
})
// -- RESTRICTED --
.state("restricted", {
abstract: true,
url: "",
views: {
'main_header': {
templateUrl: 'app/shared/header/headerView.html',
controller: 'main_headerCtrl'
},
'main_sidebar': {
templateUrl: 'app/shared/main_sidebar/main_sidebarView.html',
controller: 'main_sidebarCtrl'
},
'': {
templateUrl: 'app/views/restricted.html'
}
},
resolve: {
deps: ['$ocLazyLoad', function ($ocLazyLoad) {
return $ocLazyLoad.load([
'lazy_uikit',
'lazy_selectizeJS',
'lazy_switchery',
'lazy_prismJS',
'lazy_autosize',
'lazy_iCheck',
'lazy_themes',
'lazy_style_switcher',
'sweetAlert'
]);
}]
}
})
app.js (Angular)
....
// Satellizer configuration that specifies which API
// route the JWT should be retrieved from
$authProvider.loginUrl = 'zz/zz/laravel/public/api/authenticate';
// Redirect to the auth state if any other states
// are requested other than users
$urlRouterProvider.otherwise('/auth');
....
Laravel authenticateController (Laravel 5.2)
public function authenticate(Request $request)
{
$credentials = $request->only('email', 'password');
try {
// verify the credentials and create a token for the user
if (!$token = JWTAuth::attempt($credentials)) {
return response()->json(['error' => 'invalid_credentials'], 401);
}
} catch (JWTException $e) {
// something went wrong
return response()->json(['error' => 'could_not_create_token'], 500);
}
$user = Auth::user();
//$user->company;
//$street = $user->company->street;
// if no errors are encountered we can return a JWT
return response()->json([
"token" => $token,
"user" => $user
]);
}
What i would like to accomplish is creating a laravel Middleware for Angular so that we can check if a user is authenticated and in the future he or she has the right user role to load a Angular state.
Thanks for taking time for my issue. I'm looking forward to see how you would handle this :)
I found this solution somewhere on SO a while ago and it suits my needs(cant provide original answer yet, credits to the original answer). Event hough I use $routeProvider, I think you can apply similar logic with $stateProvider
.config(function ($routeProvider) {
$routeProvider
.when('/awesomeRoute', {
templateUrl: 'views/view.tpl.html',
controller: 'someAwesomeCtrl',
controllerAs: 'someCtrl',
resolve : {
//This function is injected with the AuthService where you'll put your authentication logic
'auth' : function(AuthService){
return AuthService.authenticate();
}
}
})
})
The Auth service:
.factory('AuthService', function($q, Service, $window, $location){
return {
authenticate : function(){
//Authentication logic here, some service to check
against backend if token you provided is valid and/or you are authorized to enter those sections of application,
or in your case with satellizer you can use `$auth.isAuthenticated()` method.
Service.isTokenValid(_yourToken).then( (res) => {
if(res.data === true){
return true;
} else {
return $q.reject('Not Authenticated');
}
}, (err) => {
$location.path('/');
return $q.reject('Not Authenticated');
})
} else {
return $q.reject('Not Authenticated');
}
}
};
})
and finally the "route error catcher", when there is something wrong, return home or to /auth route
.run(function($rootScope, $location){
//If the route change failed due to authentication error, redirect them out
$rootScope.$on('$routeChangeError', function(event, current, previous, rejection){
if(rejection === 'Not Authenticated'){
$location.path('/');
}
});
})
Related
I'm trying to check permissions on routes in an AngularJS application. Routes are handled by angular-ui-router.
My routes and permissions-per-route are defined as this:
angular.module('my_app')
.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('root', {
abstract: true,
templateUrl: 'content.html',
resolve: {
user_info: function (UserService) {
return UserService.user_info();
}
}
})
.state('root.some_page', {
url: '/',
templateUrl: 'some_page.html',
controller: 'home',
data: {
roles: ['some_right', 'some_other_right]
}
});
}
I then check for permissions as this:
[...].run(function ($rootScope, $state, UserService) {
$rootScope.$on('$stateChangeStart',
function (event, toState) {
if (!!toState.data) {
var required_roles = toState.data.roles;
if (!UserService.has_permission_in(required_roles)) {
event.preventDefault();
$state.go('root.access_forbidden');
}
}
});
});
All good for now. My issue is resolving user permissions. There are provided by a backend and fetched using $http.get(...). Since the $http service returns a promise, it don't always have the result when the $stateChangeStart is event triggered.
It's "good" when the application is "warm", but if I land on a protected page, it fails (the data are not ready). I have no way to "wait" for the promise.
What would be a "good" way of doing access control on routes?
In your protected state's definition:
.state('root.some_page', {
url: '/',
templateUrl: 'some_page.html',
controller: 'home',
resolve: {
access: function($http, $state){ //Note: you can inject your own services/factories too
var requiredPermissions = ['some_right', 'some_other_right'];
return $http.get(...).then(function(response){
var permissions = response.data.permissions;
var granted = permissions.reduce((prev,curr) => requiredPermissions.indexOf(curr) > -1 || prev, false);
if(!granted)
$state.go('publicState');
return permissions;
});
}
}
})
Resolve waits for promises to settle before moving to a new state.
Better:
function checkPermissions(requiredPermissions){
return function ($http, $state){ //Note: you can inject your own services/factories too
return $http.get(...).then(function(response){
var permissions = response.data.permissions;
var granted = permissions.reduce((prev,curr) => requiredPermissions.indexOf(curr) > -1 || prev, false);
if(!granted)
$state.go('publicState');
return permissions;
});
}
}
//In state definition
.state('root.some_page', {
url: '/',
templateUrl: 'some_page.html',
controller: 'home',
resolve: {
access: checkPermissions(['first','second'])
}
})
For some reason after adding:
.state('root', {
abstract: true,
url: '',
views: {
'layout': {
templateUrl: '../views/layout.html'
},
'header#root': {
templateUrl: '../views/shared/header.html'
},
'footer#root': {
templateUrl: '../views/shared/footer.html'
}
}
})
it stopped redirecting if the user is not logged in.
here is the full code:
angular
.module('authApp', ['ui.router', 'ui.bootstrap', 'ngAnimate', 'satellizer', 'smart-table'])
.config(function ($stateProvider, $urlRouterProvider, $authProvider, $httpProvider, $provide) {
function redirectWhenLoggedOut($q, $injector) {
return {
responseError: function (rejection) {
// Need to use $injector.get to bring in $state or else we get
// a circular dependency error
var $state = $injector.get('$state');
// Instead of checking for a status code of 400 which might be used
// for other reasons in Laravel, we check for the specific rejection
// reasons to tell us if we need to redirect to the login state
var rejectionReasons = ['token_not_provided', 'token_expired', 'token_absent', 'token_invalid'];
// Loop through each rejection reason and redirect to the login
// state if one is encountered
angular.forEach(rejectionReasons, function (value, key) {
if (rejection.data.error === value) {
// If we get a rejection corresponding to one of the reasons
// in our array, we know we need to authenticate the user so
// we can remove the current user from local storage
localStorage.removeItem('user');
// Send the user to the auth state so they can login
$state.go('root.login');
}
});
return $q.reject(rejection);
}
}
}
// Setup for the $httpInterceptor
$provide.factory('redirectWhenLoggedOut', redirectWhenLoggedOut);
// Push the new factory onto the $http interceptor array
$httpProvider.interceptors.push('redirectWhenLoggedOut');
$authProvider.loginUrl = '/api/authenticate';
$urlRouterProvider.otherwise('/login');
$stateProvider
.state('root', {
abstract: true,
url: '',
views: {
'layout': {
templateUrl: '../views/layout.html'
},
'header#root': {
templateUrl: '../views/shared/header.html'
},
'footer#root': {
templateUrl: '../views/shared/footer.html'
}
}
})
.state('root.login', {
url: '/login',
templateUrl: '../views/authView.html',
controller: 'AuthController as auth'
})
.state('root.account', {
url: '/account',
templateUrl: '../views/account.html',
controller: 'UserController as user'
})
.state('root.dashboard', {
url: '/dashboard',
templateUrl: '../views/dashboard.html',
controller: 'UserController as user'
})
.state('root.report', {
url: '/report',
templateUrl: '../views/account.html',
controller: 'UserController as user'
});
})
.run(function ($rootScope, $state, $location) {
// $stateChangeStart is fired whenever the state changes. We can use some parameters
// such as toState to hook into details about the state as it is changing
$rootScope.$on('$stateChangeStart', function (event, toState) {
// Grab the user from local storage and parse it to an object
var user = JSON.parse(localStorage.getItem('user'));
// If there is any user data in local storage then the user is quite
// likely authenticated. If their token is expired, or if they are
// otherwise not actually authenticated, they will be redirected to
// the auth state because of the rejected request anyway
if (user) {
// The user's authenticated state gets flipped to
// true so we can now show parts of the UI that rely
// on the user being logged in
$rootScope.authenticated = true;
// Putting the user's data on $rootScope allows
// us to access it anywhere across the app. Here
// we are grabbing what is in local storage
$rootScope.currentUser = user;
// If the user is logged in and we hit the auth route we don't need
// to stay there and can send the user to the main state
if (toState.name === "root.login") {
// Preventing the default behavior allows us to use $state.go
// to change states
event.preventDefault();
// go to the "main" state which in our case is dashboard
$state.go('root.dashboard');
}
}
});
});
i've added :
else{
// go to the "login" state
$state.go("root.login");
event.preventDefault();
}
in
if (user) {}
I have created a new route 'Rooms' with the generator and have modelled my $stateProvider on the admin route
.state('admin', {
url: '/admin',
templateUrl: 'app/admin/admin.html',
controller: 'AdminController',
controllerAs: 'admin',
authenticate: 'admin'
});
Vs
$stateProvider
.state('rooms', {
url: '/rooms',
templateUrl: 'app/rooms/rooms.html',
controller: 'RoomsCtrl',
controllerAs: 'rooms',
authenticate: 'admin'
});
But my route still appears without authentication!
I guess I am missing a few things to make it secure, though I am unable to understand what!
Can anyone help?
Thanks
your controller should be like:
angular.module('auth8App').controller('RoomsCtrl', function ($scope,Auth,$location) {
//check if the user is logged-in
Auth.isLoggedInAsync(function(loggedIn) {
if (!loggedIn) {
//if the user is not logged Redirect to login
event.preventDefault();
$location.path('/login');
}
});
$scope.message = 'Hello';
});
check if you have an interceptor service factorie defined and called in your app.js lik this:
.config(function ($stateProvider, $urlRouterProvider, $locationProvider, $httpProvider) {
$urlRouterProvider
.otherwise('/');
$locationProvider.html5Mode(true);
$httpProvider.interceptors.push('authInterceptor');
})
.factory('authInterceptor', function ($rootScope, $q, $cookieStore, $location) {
return {
// Add authorization token to headers
request: function (config) {
config.headers = config.headers || {};
if ($cookieStore.get('token')) {
config.headers.Authorization = 'Bearer ' + $cookieStore.get('token');
}
return config;
},
// Intercept 401s and redirect you to login
responseError: function(response) {
if(response.status === 401) {
$location.path('/login');
// remove any stale tokens
$cookieStore.remove('token');
return $q.reject(response);
}
else {
return $q.reject(response);
}
}
};
})
.run(function ($rootScope, $location, Auth) {
// Redirect to login if route requires auth and you're not logged in
$rootScope.$on('$stateChangeStart', function (event, next) {
Auth.isLoggedInAsync(function(loggedIn) {
if (next.authenticate && !loggedIn) {
event.preventDefault();
$location.path('/login');
}
});
});
});
In my AngularJS SPA, I would like to force a sort of splash page with a login form. Once the user is authenticated, then I would like to load the full website.
Here is my app.config, which currently triggers a modal logon. However, this is not the right solution for my application. I do not want to load any nav bars UNTIL the user is fully logged in.
angular
.module('rage')
.config(config);
function config($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/dashboard");
$stateProvider
.state('corp', {
url: "/corp",
templateUrl: "app/views/corp.html",
data: { pageTitle: 'RAGE' }
})
.state('maint', {
url: "/maint",
templateUrl: "app/views/maint.html",
data: { pageTitle: 'RAGE' }
})
.state('main', {
url: "/dashboard",
templateUrl: "app/views/dashboard.html",
controller: 'MainCtrl',
data: { pageTitle: 'RAGE', requireLogin: true },
resolve: {
authUser: ['$rootScope', 'loginService', 'userService', function ($rootScope, loginService, userService, authUser) {
return loginService.loginModal().then(function (user) {
$rootScope.userID = user.userId;
initSession(user, $rootScope, loginService, userService);
return user;
})
}]
}
})
.state('login', {
url: "/login",
templateUrl: "app/components/login/login.html",
controller: 'LoginCtrl'
})
}
function initSession(user, $rootScope, loginService, userService) {
userService.getInitParams().then(function (envJson) {
$rootScope.rageSessionVars = envJson;
userService.initRazor(envJson).then(function (data) {
var response = data.status;
if (response.match(/SUCCESS/g)) {
userService.openUserSession(razorEnvJson).then(function (data) {
// ...
});
}
});
});
}
Should I handle this in my app.config ?
Any advice is appreciated.
thanks,
Bob
You can do one thing.Make a state like below.
state("context", {
url:"/context",
template: '',
controller : "contextCtrl"
}).
And in 'contextCtrl' you should check if the user is authenticated or not like below
app.controller("contextCtrl",function($scope, $rootScope, userLoginService,$state, SessionService){
if(SessionService.getSessionUser()==null){
$state.go("login");
}else{
if(SessionService.getSessionUser().authorities[0].authority=="ROLE_ADMIN"){
$state.go("dashboard");
}
}
});
SessionService in above code is your localStorageService which stores the logged in user. Now in dashBoardCtrl of dashboard state (MainCtrl in your case) you should check on every request whether the user is authenticated or not. Following service checks the response of every request.If the response's status is 403,page will be redirected to login page.
service.factory('APIInterceptor', function($q) {
return {
'response': function(response) {
// do something on error
if(response.status == 302 ){
window.location.href = contextPath;
return $q.reject(response);
}
return response;
},
'responseError': function (rejection) {
if(rejection.status === 0) {
location.reload();
}
if(rejection.status == 403 ){
window.location.href = contextPath+'/#/login';
return $q.reject(response);
}
return $q.reject(rejection);
}
};
})
With this,if the user is not authenticated he will be redirected to login page.Hope it helps you.
I would like to have two home pages, the first would be for users who have not logged in and the second for users that are logged in.
This is my current set up:
.config(function ($stateProvider, $urlRouterProvider, $locationProvider, $httpProvider) {
$urlRouterProvider
.otherwise('/');
$locationProvider.html5Mode(true);
$httpProvider.interceptors.push('authInterceptor');
})
.factory('authInterceptor', function ($rootScope, $q, $cookieStore, $location) {
return {
// Add authorization token to headers
request: function (config) {
config.headers = config.headers || {};
if ($cookieStore.get('token')) {
config.headers.Authorization = 'Bearer ' + $cookieStore.get('token');
}
return config;
},
// Intercept 401s and redirect you to login
responseError: function(response) {
if(response.status === 401) {
$location.path('/login');
// remove any stale tokens
$cookieStore.remove('token');
return $q.reject(response);
}
else {
return $q.reject(response);
}
}
};
})
.run(function ($rootScope, $location, Auth) {
// Redirect to login if route requires auth and you're not logged in
$rootScope.$on('$stateChangeStart', function (event, next) {
if (next.authenticate && !Auth.isLoggedIn()) {
$location.path('/login');
}
});
});
.config(function ($stateProvider) {
$stateProvider
.state('main', {
url: '/',
templateUrl: 'app/main/main.html',
controller: 'MainCtrl',
title: 'Home',
mainClass: 'home',
headerSearch: true
});
});
How could I reconfigure this so I could do something like the following:
.config(function ($stateProvider) {
$stateProvider
.state('welcome', {
url: '/',
templateUrl: 'app/welcome/welcome.html',
controller: 'WelcomeCtrl',
title: 'Welcome',
mainClass: 'welcome',
isLoggedIn: false
});
$stateProvider
.state('main', {
url: '/',
templateUrl: 'app/main/main.html',
controller: 'MainCtrl',
title: 'Home',
mainClass: 'home',
isLoggedIn: true
});
});
Just wanted to show, how we can manage authentication driven access to states. Based on this answer and its plunker, we can enrich each state (which should be accessible only for authenticated users) with a data setting, explained here: Attach Custom Data to State Objects (cite:)
You can attach custom data to the state object (we recommend using a data property to avoid conflicts)...
So let's have some states with public access:
// SEE no explicit data defined
.state('public',{
url : '/public',
template : '<div>public</div>',
})
// the log-on screen
.state('login',{
url : '/login',
templateUrl : 'tpl.login.html',
controller : 'UserCtrl',
})
...
And some with private access:
// DATA is used - saying I need authentication
.state('some',{
url : '/some',
template : '<div>some</div>',
data : {requiresLogin : true }, // HERE
})
.state('other',{
url : '/other',
template : '<div>other</div>',
data : {requiresLogin : true }, // HERE
})
And this could be hooked on on the state change:
.run(['$rootScope', '$state', 'User', function($rootScope, $state, User)
{
$rootScope.$on('$stateChangeStart'
, function(event, toState, toParams, fromState, fromParams) {
var isAuthenticationRequired = toState.data
&& toState.data.requiresLogin
&& !User.isLoggedIn
;
if(isAuthenticationRequired)
{
event.preventDefault();
$state.go('login');
}
});
}])
See all that in action here
There is similar Q & A were I try to show the concept of redirecting Authenticated and Not Authenticated user:
Angular UI Router: nested states for home to differentiate logged in and logged out
maybe that could help to get some idea, how we can use ui-router, and its event '$stateChangeStart' to hook on our decision manager - and its forced redirecting...
the code should be something like this
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState) { //, fromParams
console.log(toState.name + "," + fromState.name);
if(fromState.name === "") {
if (Auth.isLoggedIn()) {
$state.go('welcome');
event.preventDefault();
} else {
$state.go('home');
event.preventDefault();
} else {
if (toState.authenticate && !Auth.isLoggedIn()) {
$toState.go('login');
event.preventDefault();
}
}
}
so if user entering the application, then if he is logged in take him to welcome else take him to home.
once he is inside, then if he hits some route which needs auth.. then redirect him to login page..
sorry if i did not understood you requirement fully...
.config(function ($stateProvider,$rootScope) {
$stateProvider
.state('welcome', {
url: '/',
templateUrl: 'app/welcome/welcome.html',
controller: 'WelcomeCtrl',
onEnter: function() {
if (userIsLoggedIn()) {
$stateProvider.go('home');
}
});
});
I had problem like this and I solved it like this
.run(function ($rootScope, $location, AuthService) {
// start showing PRELOADER because we doing async call and we dont know when it will be resolved/rej
AuthService.checkLoginStatus().then(
(resp) => {
// fire logged in user event
$rootScope.$broadcast('userLogged',resp);
$location.path(YourHomePageUrl);
},
(err)=> {
// Check that the user is not authorized redirect to login page
$location.path(loginURL);
}
}).finally(
()=> {
// finnaly Set a watch on the $routeChangeStart
/// Hide PRELOADER
$rootScope.$on('$routeChangeStart',
function (evt, next, curr) {
if (!AuthService.isLoggedIn()) {
$location.path(art7.API.loginURL);
}
});
}
)
});
and im not using interceptor for handling 401 not authorized errors, thats my solution