Identity authentication can be applied in the web. This article introduces the skills of AngularJS to apply identity authentication. Let’s read it down without saying much nonsense.
Identity Authentication
The most common way of identity authentication is to use the user name (or email) and password to log in. This means implementing a login form so that users can log in with their personal information. This form looks like this:
<form name="loginForm" ng-controller="LoginController" ng-submit="login(credentials)" novalidate> <label for="username">Username:</label> <input type="text" ng-model=""> <label for="password">Password:</label> <input type="password" ng-model=""> <button type="submit">Login</button> </form>
Since this is an Angular-powered form, we use the ngSubmit directive to trigger the function when uploading the form. One thing to note is that we pass personal information into the function that uploads the form instead of directly using the $ object. This makes it easier for the function to perform unit-test and reduces the coupling of this function to the current Controller scope. This Controller looks like this:
.controller('LoginController', function ($scope, $rootScope, AUTH_EVENTS, AuthService) { $ = { username: '', password: '' }; $ = function (credentials) { (credentials).then(function (user) { $rootScope.$broadcast(AUTH_EVENTS.loginSuccess); $(user); }, function () { $rootScope.$broadcast(AUTH_EVENTS.loginFailed); }); };javascript:void(0); })
We noticed that there is a lack of practical logic here. This Controller is made like this, with the purpose of decoupling the logic of identity authentication from the form. It's a good idea to pull the logic out of our Controller as much as possible and put them all into services. AngularJS Controller should only manage objects in $scope (using watching or manual operation) rather than taking on too much and too heavy things.
Notify session changes
Identity authentication affects the status of the entire application. For this reason, I would recommend using events (using $broadcast) to notify user session changes. It is a good choice to define all possible event codes in one middle. I like to use constants to do this:
.constant('AUTH_EVENTS', { loginSuccess: 'auth-login-success', loginFailed: 'auth-login-failed', logoutSuccess: 'auth-logout-success', sessionTimeout: 'auth-session-timeout', notAuthenticated: 'auth-not-authenticated', notAuthorized: 'auth-not-authorized' })
There is a good feature of constants that they can be injected anywhere else, just like services. This makes constants easy to be called by our unit-test. constants also allow you to easily rename them later without changing a large string of files. The same trick is used in user roles:
.constant('USER_ROLES', { all: '*', admin: 'admin', editor: 'editor', guest: 'guest' })
If you want to give editors and administrators the same permissions, you just need to simply change ‘editor’ to ‘admin’.
The AuthService
The logic related to authentication and authorization (access control) is best placed in the same service:
.factory('AuthService', function ($http, Session) { var authService = {}; = function (credentials) { return $http .post('/login', credentials) .then(function (res) { (, , ); return ; }); }; = function () { return !!; }; = function (authorizedRoles) { if (!(authorizedRoles)) { authorizedRoles = [authorizedRoles]; } return (() && () !== -1); }; return authService; })
To further away from the concerns of authentication, I use another service (a singleton object, using the service style) to save the user's session information. The information details of the session depend on the implementation of the backend, but I'll give a more common example:
.service('Session', function () { = function (sessionId, userId, userRole) { = sessionId; = userId; = userRole; }; = function () { = null; = null; = null; }; return this; })
Once the user is logged in, his information should be displayed in some places (such as the user's avatar in the upper right corner). To achieve this, the user object must be referenced by the $scope object, better still a place that can be called globally. While $rootScope is obviously the first choice, I tried to restrain myself, but use $rootScope more often (I actually only use $rootScope for global event broadcasts). To do this the way I like is to define a controller at the root node of the application, or elsewhere at least higher than the Dom tree. Tags are a great choice:
<body ng-controller="ApplicationController"> ... </body>
ApplicationController is a container of the global logic applied and a choice of run methods for running Angular. So it is at the root of the $scope tree, and all other scopes will inherit it (except for the isolation scope). This is a good place to define the currentUser object:
.controller('ApplicationController', function ($scope, USER_ROLES, AuthService) { $ = null; $ = USER_ROLES; $ = ; $ = function (user) { $ = user; }; })
We don't actually assign the currentUser object, we just initialize the properties on the scope so that the currentUser can be accessed later. Unfortunately, we cannot simply assign a new value to the currentUser in the subscope because that would cause shadow property. This is the result of passing the original type as a value (strings, numbers, booleans, undefined and null) instead of passing the original type with reference. To prevent shadow property, we need to use the setter function. For more information on Angular scopes and prototype inheritance, read Understanding Scopes.
Access control
Identity authentication, that is, access control, does not actually exist in AngularJS. Because we are client applications, all the source code is in the hands of users. There is no way to prevent users from tampering with code to obtain a certified interface. All we can do is display control. If you need real identity authentication, you need to do this on the server side, but this is beyond the scope of this article.
Restrict the display of elements
AngularJS has instructions to control display or hide elements based on scope or expressions: ngShow, ngHide, ngIf and ngSwitch. The first two will use a <style> attribute to hide the element, but the last two will remove the element from the DOM.
The first method, that is, hide elements, is best used on elements whose expressions are frequently changed and do not contain too much template logic and scope references. The reason is that in hidden elements, the template logic of these elements will still be recalculated in each digest loop, causing application performance to decline. The second way is to remove the element, and it will also remove all handler and scope bindings on this element. Changing the DOM is a lot of work for the browser (in some scenarios, compared to ngShow/ngHide), but in many cases this price is worth it. Because user access information does not change frequently, using ngIf or ngShow is the best choice:
<div ng-if="currentUser">Welcome, {{ }}</div> <div ng-if="isAuthorized()">You're admin.</div> <div ng-switch on=""> <div ng-switch-when="">You're admin.</div> <div ng-switch-when="">You're editor.</div> <div ng-switch-default>You're something else.</div> </div>
Restrict routing access
Many times you want to make the entire web page unaccessible, rather than just hiding an element. If we can rerout (in UI Router, routing is also called state) using a custom data structure, we can clarify which user roles can be allowed to access what content. The following example uses the style of UI Router, but these also apply to ngRoute.
.config(function ($stateProvider, USER_ROLES) { $('dashboard', { url: '/dashboard', templateUrl: 'dashboard/', data: { authorizedRoles: [USER_ROLES.admin, USER_ROLES.editor] } }); })
Next, we need to check each routing change (that is, when the user jumps to other pages). This requires listening to events from $routeChangStart (in ngRoute) or $stateChangeStart (in UI Router):
.run(function ($rootScope, AUTH_EVENTS, AuthService) { $rootScope.$on('$stateChangeStart', function (event, next) { var authorizedRoles = ; if (!(authorizedRoles)) { (); if (()) { // user is not allowed $rootScope.$broadcast(AUTH_EVENTS.notAuthorized); } else { // user is not logged in $rootScope.$broadcast(AUTH_EVENTS.notAuthenticated); } } }); })
Session time
Identity authentication is mostly a server-side matter. No matter what implementation method you use, your backend will do real verification of user information and handle processing such as Session timeliness and access control. This means that your API will sometimes return some authentication errors. Is the standard error code the HTTP status? These error codes are commonly used:
- 401 Unauthorized — The user is not logged in
- 403 Forbidden — The user is logged in but isn't allowed access
- 419 Authentication Timeout (non standard) — Session has expired
- 440 Login Timeout (Microsoft only) — Session has expired
The latter two are not standard content, but may be widely used. The best official judgment session The expired error code is 401. In any case, your login dialog should be displayed immediately when the API returns 401, 419, 440, or 403. Overall, we want to broadcast and based on these HTTP return codes for the time, for which we add an interceptor in $httpProvider:
.config(function ($httpProvider) { $([ '$injector', function ($injector) { return $('AuthInterceptor'); } ]); }) .factory('AuthInterceptor', function ($rootScope, $q, AUTH_EVENTS) { return { responseError: function (response) { $rootScope.$broadcast({ 401: AUTH_EVENTS.notAuthenticated, 403: AUTH_EVENTS.notAuthorized, 419: AUTH_EVENTS.sessionTimeout, 440: AUTH_EVENTS.sessionTimeout }[], response); return $(response); } }; })
This is just a simple implementation of an authentication interceptor. There is a great project in Github, which does the same thing and uses the httpBuffer service. When an HTTP error code is returned, it blocks the user's further request until the user logs in again and then continues the request.
Login dialog command
When a session expires, we need the user to re-enter his account. To prevent him from losing his current job, the best way is to pop up the login dialog instead of jumping to the login page. This dialog needs to listen for notAuthenticated and sessionTimeout events, so when one of the events is triggered, the dialog box needs to be opened:
.directive('loginDialog', function (AUTH_EVENTS) { return { restrict: 'A', template: '<div ng-if="visible" ng-include="\'\'">', link: function (scope) { var showDialog = function () { = true; }; = false; scope.$on(AUTH_EVENTS.notAuthenticated, showDialog); scope.$on(AUTH_EVENTS.sessionTimeout, showDialog) } }; })
This dialog box can be expanded as long as you like. The main idea is to reuse existing login form templates and LoginController. You need to write the following code on each page:
<div login-dialog ng-if="!isLoginPage"></div>
Note the isLoginPage check. A failed login triggers notAuthenticated time, but we don't want to display this dialog on the login page because it's redundant and weird. This is why we don't put the login dialog box on the login page as well. So it is reasonable to define a $ in the ApplicationController.
Save user status
When users refresh their page and still save logged-in user information is a cunning link in single-page application authentication. Because all states exist on the client, refreshing will clear user information. To fix this, I usually implement an API that returns the data of the logged-in current user (such as /profile), which will be launched in the AngularJS application (such as in the "run" function). The user data will then be saved in the Session service or $rootScope, just like the status after the user has logged in. Alternatively, you can embed user data directly into , so there is no need for additional requests. The third way is to include user data in cookies or LocalStorage, but this will make it a little more difficult to log out or clear user data.
at last……
I am a little bit of experience in my talent. This is a translated article. If there are any fallacies, please feel free to correct them.
The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.