Git Product home page Git Product logo

angular-login-example's Introduction

angular-login-example Bitdeli Badge

Stand-alone project showing how to make a robust angular application serving access permissions from Server.

Features in modern releases

A new (polishing) release has been in work for the last months, differences are only in the demo app, not in the service.

  • You can now add users in the demo, and they gets displayed in homepage.
  • Style is way more polished, I hope you can find useful snippets out of it.
  • Registration form provides a nice error feedback, you can dig into the code for the details.
  • All the ngMock requests gets printed in the console!

Table of Contents

Start the project locally

$ git clone https://github.com/mrgamer/angular-login-example.git
$ cd angular-login-example
$ npm install && bower install && grunt

# Open browser on http://localhost:8080

TL;DR i wanna make it work asap!

Clone the repo, and try declaring a new state with How to declare a state; altough i recommend reading some of the page in order to understand the implications of what you're doing!

What is this example for:

This project is an example about how to build a very robust login system using AngularJS. It manages tokens for users to login, they can be managed customly server-side (server side is absent on this demo), you can make them expire/change permissions without worrying about leaving a non-consistant state on the client side.

PLEASE NOTE: this is an example because it's just a demostration, and can act as a starting point for your implementation, if you're looking for a library that gets login done in your AngularJS application in 5 minutes, this is not what this project is about!

but there are other...?

Yes! There are other projects trying to cover authentication limitations of AngularJS vs server-side solutions, i would recommend fnakstad's project, and his blogposts. There is also another project about managing non-authorized http requests.

The main differences in fnakstad's implementation against this one, is that he serves a cookie upfront when serving the index.html page; this cookie has all the information needed regarding the user permissions.

I really didn't want to mess with cookies in my client-side code.
A note about cookies: they are handled in an "uncommon" way in ExpressJS, they are not "rolling-cookies", it's a clever implementation to me, but it's not what a php/.net developer expects (or your boss expects!).

Secondly, this works with a RESTful API service!

functionality my implementation fnakstad's
Simplicity
No dependencies
Easier compatibility
No server changes
RESTful support
Handles errors
"hackish"
less code recursion

Token Revocation

In a decently-sized application user banning and authorization revocation might be an important of the login department.
Doing this using cookies it's tricky, ExpressJS or any other backend, usually doesn't give you direct access to the cookie Array.

The usual workaround is to write an userToken inside the cookie and then revoke that token, when revoked you have to clear the cookie.
As personal taste, i haven't found it particularly elegant, so a more radical/direct approach.

Libraries used

angular.js

You should know this one :-) In this release the dev team has put routing in a separate file because there are alternative projects like...

angular-ui-router

This is the star here! After messing my life using libraries like backbone-layoutmanager, i can say this is a far more thinked solution, incredibly stable, and elegant solution to be only at version 0.2.0!

angular-mocks

This part of AngularJS kit is made mainly for testing purposes, on this demo is used to simulate a backend server with 700ms response time.

loginService

Overridable Properties

The following properties are overridable at config time, injecting loginServiceProvider.

Example:

angular.module('myapp.module', [])
.config(function (loginServiceProvider) {
  errorState = 'myapp.OriginalWayToError';
  userToken = '9088mmmll18992jn';
});

userToken

Default value is obtained through localStorage.getItem('userToken').
You can override this and use cookies, sqlite, anything custom works aswell (from URL? Ex: /somepath?userToken=XX992mm2Yy1m ).

errorState

It's a string, the name of a the default state that handles the $stateChangeError.
In the example, this state comes with a parameter in the URL, PLEASE NOTE that a state must have a parameter in the url, even if it the parameters gets passed by $state.go (and not writing them actually in the URL).
If the parameters are not "registered" in the url, they get filtered and never reach $stateParams.

logoutState

It's a string, the name of the state the user gets redirected after the logoutUser() has been processed.

Private Methods

getLoginData

Function that gets called on the first initialization of the provider.
It reads the userToken, and if it's set, sets the $http headers.

setHeaders

Function called by the previous one, and by the next one.
It is a setter for $http.defaults.headers, nothing more.

setToken

Function that registers the user in the localStorage (in this implementation).
Then calls setHeaders, to make sure headers are coherent with the token given.

managePermissions

Function that gets called on the first initialization of the provider.
Registers the listeners on $stateChangeStart, $stateChangeSuccess, $stateChangeError, in order to manage permissions and error redirection.

$stateChangeStart

Synchronous check on permissions, if the service already has the informations about the user (in short: if it's an anonymous user), authorize or denies it. Handles spinner appearance.

$stateChangeSuccess

Handles spinner disappear.

$stateChangeError

Manages error redirection in case of any resolve fails, even the grandfather one.

Public Methods

loginHandler

Important function that gets called once the grandfather receives the informations from the server.
Arguments should be function (data, status, headers, config) the same as $http.success/$http.error

It updates loginService.user with the JSON (that should be) in the first argument.

This is the place where to put custom logic in case you don't have a server that gives you a correct userRole, you can generate it manually looking at other (custom) informations given by the server response.

loginUser

Accepts as only argument an httpPromise, it's usually called from a controller, and simply calls the above loginHandler (for now).

logoutUser

Accepts as only argument an httpPromise, it redirects the user to the logoutState.

resolvePendingState

Function used in the grandfather resolve, it includes an asynchronous check on permissions.
Returns a $q promise that gets resolved in case the user can access the requested state, rejected otherwise.

Public Properties

userRole

This property might have 2 states: null, or a valid userRole from routing-config.js

user

This property gets updated by loginHandler, pay attention, this gets done using angular.extend.

isLogged

Boolean property indicating if the user is logged with an userRole different than userRoles.anonymous, useful to display the user status in your AngularJS application.

pendingStateChange

Boolean property indicating if the loginService is waiting for the grandfather's resolve to be completed, in order to check if the user can or cannot access the requested state.

doneLoading

Boolean property, might have 3 states: null, false, true.
null: loginService hasn't done it's work yet.
false: loginService is waiting for some $http promise to get completed
true: loginService got answer from $http so the values must be considered final.

Should be handy for displaying loading spinners, as done on this example.

Logic behind

While this demostration has some code behind, the user checking problem in a Single-Page Application is actually more a logic problem (when to check permissions? how to get required informations?), instead of a coding-problem.

grandfather, what's that?

As you can see if you meddle with the code, the so-called grandfather is an abstract state that is the father of all the states.

diagram

The state logic in angular-ui-router is based off a N-ary state tree.
The root of this tree is the grandfather, being abstract only means it gets executed but cannot be transitioned into, exactly what we need check permissions asynchronously.

routing-config, what's that?

In the app there is a file so called routing-config.js completely taken from fnakstad project.
I think it's a clever and handy bit-based security system.

synchronous and asynchronous check

In this demo you can see there is a double check on user permissions to transition to a state:

The former is a synchronous check on all the $stateChangeStart events, this must be synchronous because events for their nature can only be prevented in a sync-way. But since we need it to do a server-side request the first REAL check is done after an http request.

The latter is inside the resolvePendingstate method, called from the grandfather state in this example, just after it obtained the valid user informations to let the user access a state or not.

call schema

Here's a brief call schema of the app and the service, and how they interact with the user request.
Bare in mind this is for the user to get a general idea, the real amount of calls might be higher (or slightly different).

call schema

How to generate correct errors

resolve errors

Custom errors can be generated in your own resolve(s), for example:

angular.module('myapp.module', [])
.config(function ($stateProvider) {
  $stateProvider
    .state('app.somestate', {
      url: '/random/url',
      resolve: {
        'resourceNeeded': function ($q) {
          var strangeDeferred = $q.defer();
          if (Math.random() * 10 > 5) {
            strangeDeferred.resolve('you have been lucky');
          } else {
            if (Math.random() * 10 > 5) {
              strangeDeferred.reject('not lucky enough');
            } else {
              strangeDeferred.reject('real bad luck');
            }
          }
          return strangeDeferred.promise;
        }
      }
    });
})

Will generate a 'not lucky enough' || 'real bad luck' error, into the $stateChangeError handler.
The default behaviour is to redirect to errorState.

But if you want a redirect to a custom state, you just have to add this to the previous example:

angular.module('myapp.module', [])
.config(function ($stateProvider) {
  $stateProvider
    .state('app.somestate', {
      url: '/random/url',
      resolve: '', /* previous resolve here */
      redirectMap: {
        'not lucky enough': { state: 'app.error', prefix: 'luck' },
        'real bad luck': 'app.specialBadluck'
      }
    });
})

IMPORTANT NOTE: if you decide to use the object version, prefix is not optional, if you don't want a prefix just use ''.

The object version is mostly in case there is a state that handles many errors, and you want to differentiate them, using a string prefix.
The string version is shorter and more usable, use what's more appropriate for you.

$http errors

$http errors gets handled in a very similar way, the error is based on the statusCode converted to string given by the httpPromise.error.
Example:

angular.module('myapp.module', [])
.config(function ($stateProvider) {
  $stateProvider
    .state('app.criticalState', {
      url: '/random/url',
      resolve: {
        'httpRequestNeeded': function ($http) {
          /* let's suppose the responding statusCode is 409 */
          return $http.get('/whatever/url');
        }
      },
      redirectMap: {
        '409': { state: 'app.httpErrors', prefix: 'criticalState' }
      }
    });
});

And in the app.httpErrors's state template:

<div ng-switch="$stateParams.error">
  <p ng-switch-when="criticalState409">httpRequestNeeded has failed to complete, this is fail.</p>
  <p ng-switch-when="409">Generic request responded with a 409.</p>
  <p ng-switch-when="401">Unauthorized!</p>
  <p ng-switch-default>Random HTTP Error occurred!</p>
</div ng-switch>

Redirect Handling

This example using AngularJS 1.2.0rc1 doesn't have $routeProvider but angular-ui-router has $urlRouterProvider, as shown on app.js this is sufficient to declare redirects.

Use his very well written wiki to read howto

How to declare a state

The correct way to declare a state for your application is to have it depend on app.grandfather.
Also the state needs to be called 'app.something', because the grandfather state is called 'app' in a way to be the father of any state.

For example a section of your single-page-application might be declared as funny.js:

angular.module('myapp.funny', ['myapp.grandfather'])
.config(function ($stateProvider) {
  $stateProvider
    .state('app.funny', {
      url: '/funny',
      templateUrl: '/funny/funny.tpl.html',
      controller: 'FunnyController',
      resolve {
        'getRandomFunnyFacts': function ($http) {
          return $http.get('/funnygenerator');
        }
      },
      redirectMap: {
        'noFunFound': 'app.sadError'
      }
    });
})
.controller('FunnyController', function ($scope, getRandomFunnyFacts) {
  /* suppose the getRandomFunnyFacts is a JSON Array, we'll get the latest added funny fact */
  $scope.phrase = getRandomFunnyFacts.pop();
});

angular-login-example's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

angular-login-example's Issues

Complete registration

Greetings, awesome library!

Now, i got a question regarding the complete registration process.

if (user.hasValidatedEmail) {
  wrappedService.userRole = userRoles.registered;
} else {
   wrappedService.userRole = userRoles.invalidEmail;
   $state.go('app.nagscreen');
}

Right now i'm not changing the state, as i use a lightbox to put the validation code. My problem is, after the validation code is sent, i need to update the user 'valid_email' to ', so i can now access the dashoboard, but i don't understand how could i do this... Any idea?

Loading state behavior

I cloned the repo, everything works fine except the "loading" view. In the demo it replaces whatever is currently active before switching to the desired state, whereas for me, the loading template simply appears below whatever view I currently have active before switching to the new state.

Service

Is there some idea for getting the users by a json file on a service and not locally?

minify issue

I've been using your code along with the generator-angular project, everything works great except when i build it in dist mode, it failed on the login-service, i suspect it is the injector issue, but i've tried everything, i couldn't get the dist code to work.

File not Found

I don't know where the files are
style.css
libs.js
app.js
and templates-app.js

While i'm running in my broswer, i get an error.

All files included?

Are all the files included in the git downloads? I have tried downloading the zip and via git but there still seems to be files missing from the archive. Just as an example, templates-app.js is missing from the zip archive and I have a feeling there may be others missing.

Multiple roles per user?

Hi,
thank you so much for your work - it works really well! In the project, where I want to use your solution, we need to assign multiple roles (more 'permissions') to one user. For example, there is superadmin that has all the permissions, but there can be users having (multiple, from one to all) permissions:

  • creating x
  • viewing y
  • administration of users
  • administration of x
  • administration of y

If got this right, in current solution, I would have to create role for each combination of those permissions?

Thanks in advance, hope you are still visiting this page ;-) Cheers, Szymon.

Can I store the userRole in the localStorage?

First, thank you for your example! I learned a lot from it :)

Now, I have a question, can I store the userRole in the localStorage?
In this example, when the user requested a $state, the loginService will do the getLoginData() function, if there is a token in the localStorage, the loginService will not init the userRole ,so the pendingStateChange will be inited. Which cause the the grandfather do the resolve to retrieve the userRole remotely.

So if I init the userRole to be public and store it in the localStorage, when user do some login action, I change the value of userRole in the localStorage. By this way, can I delete the resolve in the grandfather?Will it be unsafe if I delete the resolve?

Rube Goldberg

Have you ever heard of the phrase "Rube Goldberg" job?

Token expiry

At the moment if a user logs in and a token has expired (simulated by deleting the token from tokenStorage), after refreshing the page the user will get a 401 error even if the state can be accessed by the public (e.g. home page). I think the state change should go through even if /users returns an error, as long as the state is available to public.

Handle "401" errors in resolvePendingState

Maybe I don't use it correctly but my user api returns an 401 error when called by a not logged in user. In this case resolvePendingState is not returning to a correct state. I had to add this for my app to work well in any situation :

      function error(httpObj) {
       self.doneLoading = true;
        // duplicated logic from $stateChangeStart, slightly different, now we surely have the userRole informations.
        if (pendingState.to.accessLevel === undefined || pendingState.to.accessLevel === accessLevels.public ) {
          checkUser.resolve();
        } else {
          checkUser.reject('unauthorized');
        }
      },

Access Loginservice within resolve

First off thanks for the proof of concept. I was trying to get the User Role within the resolve of my route. But is seems like it's not been set. Any suggestions?

angular.module('angular-login.pages', ['angular-login.grandfather'])
.config(function ($stateProvider) {
  $stateProvider
    .state('app.admin', {
      url: '/admin',
      templateUrl: 'pages/admin.tpl.html',
      accessLevel: accessLevels.admin
    })
    .state('app.user', {
      url: '/user',
      templateUrl: 'pages/user.tpl.html',
      accessLevel: accessLevels.user,
      resolve: {
        somedata: function (loginService, $q) {
          console.log(loginService.userRole); // is null
        }
      }
    });
});

Error while clicking twice a restricted link

Thanks for your example it really helped me figure how to secure some parts of my application. Plus it's a great intro to ui-router.
But there is a small bug I am unable to nail down: if you click twice the private link it never return to the $stateChangeSuccess.

Logout

service url som logger ut användar och raderer authtoken från er auth-tabel.

(om man användar offentlig dator eller inte vil vära kvar inloggat)

Grunt

Well, I upgraded my Mac to Mavericks and GIT commands do not work; I am pretty pissed off about that one. Is there a way to install w/o Grunt?

Your library and install seems totally out of sync. Pls see below

hi,
This sample code must have worked for you before but now is not working. I believe there is a mix up when you put it back into gibhub

within index.html

<script type="text/javascript" src="libs.js"></script>

<script type="text/javascript" src="app.js"></script>

<script type="text/javascript" src="templates-app.js"></script>

**** 2 libraries : lib.js and templates.app.js are missing based on index.html javascript preload.

instead in folders seeing : grandfather.js , routing-config.js , form-helper.js , error.js , login-service.js , morkhttp.js , pages.js , register.js

i matched up all the module names as per your injection to give that a spin

so I tried changing the index.html and preloading all of the above and also adding angular-ui-router ; it hangs at requiring 'templates-app' as one of the Inject dependency
in module : grandfather.js; do understand grandfather.js is required from decendency injection of other modules pointing to 'angular-login.grandfather'

When I take out the injection dependency 'templates-app' in the grandfather.js , then no more complaining of injection error.. but then get

Error: Unexpected request: GET home.tpl.html
and another error: TypeError: Cannot read property 'toString' of undefined

Your link to demo looks great, and I see the HTML code should render the same if the JS code don't break; but currently is way off . Please test and update with same working version as you have on that link. let me know thanks.

I really like to see this working.

Question: if I use route-segment library instead of ui-router, would it still work?

thanks

Demo doesn't work

I have npm, grunt, nodejs installed but when I run these commands:

$ git clone https://github.com/mrgamer/angular-login-example.git
$ cd angular-login-example
$ npm install && grunt && npm start

# Open browser on http://localhost:8080

The demo on http://localhost:8080 doesn't work. I tried it in Google Chrome on Ubuntu and Windows. Google Chrome console:

Failed to load resource: the server responded with a status of 404 (Not Found) http://localhost:8080/style.css
Uncaught ReferenceError: angular is not defined app.js:1
Uncaught ReferenceError: angular is not defined 

AngularJS isn't even included. Am I missing something here?

Question regarding loginService.user object

I've got a service that needs to access the user object stored in login service. Is there a way to wait for the user object to become ready?

angular.module('apiService', [])
    .factory('apiService', function (loginService) {
        console.log(loginService.user);
   });

This returns {} in the console log. I think I can use localstorage, but I'm sure I'm missing something.

Thanks

privacy maybe?

Before showing email addresses in public like that after someone registers in the example, Can you at least set a small warning about that "Your emaill address and login credentials will be shared to the public" ?
If you can please remove our credentials from the homepage.
Thanks in advanced.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.