Many times in the Web can be applied to identity authentication, this article introduced the ANGULARJS application of identity authentication skills, nonsense not to say a look down together.
Identity Certification
The most common way of identity authentication is to use the user name (or email) and password to do the login operation. This means implementing a landing form so that users can log in with their personal information. The form looks like this:
<form name= "LoginForm" ng-controller= "Logincontroller" ng-submit=
"Login (Credentials)" Novalidate>
<label for= "username" >Username:</label>
<input type= "text" id= "username" ng-model= "
Credentials.username ">
<label for=" password ">Password:</label>
<input type=" Password " id= "Password"
ng-model= "Credentials.password" >
<button type= "Submit" >Login</button>
</form>
Since this is a angular-powered form, we use the ngsubmit instruction to trigger the function when uploading the form. Note that we pass personal information to a function that uploads the form instead of using $scope directly. Credentials this object. This makes the function easier to unit-test and reduces the coupling of this function with the current Controller scope. The Controller looks like this:
. Controller (' Logincontroller ', function ($scope, $rootScope, auth_events, Authservice) {
$scope. Credentials = {
username: ',
Password: '
};
$scope. Login = function (credentials) {
authservice.login (credentials). Then (function (user) {
$rootScope. $ Broadcast (auth_events.loginsuccess);
$scope. Setcurrentuser (user);
}, Function () {
$rootScope. $broadcast (auth_events.loginfailed);
Javascript:void (0);
})
We note that there is a lack of practical logic here. This Controller is made so that the logic of identity authentication is decoupled from forms. 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 within $scope (watching or manual operation) rather than taking on too much weight.
Notification of changes in session
Identity authentication affects the state of the entire application. For this reason I recommend using events ($broadcast) to notify the user of the change in session. It's a good idea to define all the possible event codes in a middle zone. I like to use constants to do this thing:
. 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 '
})
A good feature of constants is that they can be easily injected into other places, just like services do. This makes constants very easy to be called by our unit-test. Constants also allows you to easily rename them later without having to change a large string of files. The same trick was applied to 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 associated with identity authentication and authorization (access control) is best placed in the same service:
. Factory (' Authservice ', function ($http, session) {
var authservice = {};
Authservice.login = function (credentials) {return
$http
. Post ('/login ', credentials)
. Then (function ( RES) {
session.create (res.data.id, res.data.user.id,
res.data.user.role);
return res.data.user;
};
authservice.isauthenticated = function () {return
!! Session.userid;
};
authservice.isauthorized = function (authorizedroles) {
if (!angular.isarray (authorizedroles)) {
Authorizedroles = [authorizedroles];
}
Return (authservice.isauthenticated () &&
authorizedroles.indexof (session.userrole)!==-1);
return authservice;
})
To further away from the concerns of identity authentication, I used another service (a single object, using the service style) to save the user's session information. The details of the session are dependent on the backend implementation, but I'll give you a more general example:
. Service (' Session ', function () {
this.create = function (SessionId, userId, userrole) {
this.id = sessionid;
this.userid = userId;
This.userrole = userrole;
};
This.destroy = function () {
this.id = null;
This.userid = null;
This.userrole = null;
return this;
})
Once the user has logged in, his message should be displayed in some places (such as the avatar in the upper right-hand corner). To achieve this, the user object must be $scope object reference, preferably a place that can be called globally. While $rootScope is the obvious first choice, I try to restrain myself from using the $rootScope too much (I'm actually only using $rootScope on global event broadcasts). To do this in the way I like, is to define a controller at the root node of the application, or at least in any place above the Dom tree. The label is a good choice:
<body ng-controller= "Applicationcontroller" > ...
</body>
Applicationcontroller is the container for applied global logic and the choice of a run method for running angular. So it is at the root of the $scope tree, and all other scopes inherit it (except for the isolation scope). It's a great place to define the CurrentUser object:
. Controller (' Applicationcontroller ', function ($scope,
user_roles,
authservice) {
$scope. CurrentUser = null;
$scope. userroles = user_roles;
$scope. isauthorized = authservice.isauthorized;
$scope. Setcurrentuser = function (user) {
$scope. currentUser = user;}
)
We don't actually assign CurrentUser objects, we simply initialize the properties on the scope so that CurrentUser can be accessed later. Unfortunately, we cannot simply assign a new value to currentUser in the child scope because that would result in shadow property. This is the result of the value passing the original type (strings, numbers, booleans,undefined and null) instead of passing the original type by reference. To prevent shadow property, we use the setter function. If you want to learn more about angular scopes and prototype inheritance, please read understanding scopes.
Access control
Identity authentication, that is, access control, in fact, does not exist in Angularjs. Because we are the client application, all the source code in the user's hand. There is no way to prevent users from tampering with the code to obtain a certified interface. All we can do is show control. If you need real identity authentication, you need to do it on the server side, but this is beyond the scope of this article.
Restricting the display of elements
ANGULARJS has commands based on scopes or expressions to control the display or hidden elements: ngshow, Nghide, Ngif, and Ngswitch. The first two will use a <style> attribute to hide the element, but the latter two will remove the element from the DOM.
The first way, which is to hide elements, is best used for expressions that change frequently and do not contain excessive template logic and scope references. The reason is that in hidden elements, the template logic of these elements is still recalculated in each digest cycle, allowing for the application to fall. The second way, removing elements also removes all handler and scope bindings on this element. Changing the DOM is a lot of work for browsers (in some scenarios, compared to ngshow/nghide), but in many cases the cost is worth it. Because user access information does not change frequently, using Ngif or ngshow is the best option:
<div ng-if= "CurrentUser" >welcome {currentuser.name}}</div> <div ng-if=
"IsAuthorized" ( userroles.admin) ">you ' re admin.</div>
<div ng-switch on=" Currentuser.role ">
<div Ng-switch-when= "Userroles.admin" >you ' re admin.</div>
<div ng-switch-when= "UserRoles.editor" > You ' re editor.</div>
<div ng-switch-default>you ' re something else.</div>
</div>
Restricting routing access
Many times you want to make the entire page inaccessible, rather than just hiding an element. If you can reroute (in UI Router, routing is also called state) using a custom data structure, we can identify which user roles are allowed to access which content. The following example uses the UI Router style, but the same applies to Ngroute.
. config (function ($stateProvider, user_roles) {
$stateProvider. State (' dashboard ', {
URL: '/dashboard ',
Templateurl: ' dashboard/index.html ',
data: {
authorizedroles: [User_roles.admin, User_roles.editor]
}
});
})
Next, we need to check each routing change (when the user jumps to another page). This requires listening to events in the $routeChangStart (Ngroute) or $stateChangeStart (UI Router):
. Run ($rootScope, auth_events, Authservice) {
$rootScope. $on (' $stateChangeStart ', function (event, next) {
var authorizedroles = next.data.authorizedRoles;
if (! Authservice.isauthorized (Authorizedroles)) {
event.preventdefault ();
if (authservice.isauthenticated ()) {
//user is not allowed
$rootScope. $broadcast (auth_events.notauthorized) ;
} else {
//user isn't logged in
$rootScope. $broadcast (auth_events.notauthenticated);}
}
);
})
Session limitation
Identity authentication is mostly a server-side thing. No matter how you implement it, your backend will do real validation and handling of user information such as session aging and access control. This means that your API will sometimes return some authentication errors. The standard error code is the HTTP state. Common use of these error codes:
- 401 unauthorized-the user isn't 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 expired error code is 401. In any case, your login dialog should be displayed immediately when the API returns 401, 419, 440, or 403. In general, we want to broadcast and based on these HTTP return codes time, for which we add an interceptor in the $httpProvider:
. config (function ($httpProvider) {
$httpProvider. Interceptors.push ([
' $injector ',
function ($injector {return
$injector. Get (' 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.Status], response);
Return $q. Reject (response);}}
)
This is just a simple implementation of a certified interceptor. There is a great project in Github, it does the same thing, and uses the Httpbuffer service. When an HTTP error code is returned, it blocks the user from further requests until the user logs on again, and then continues the request.
logon dialog box directives
When a session expires, we need the user to re-enter his account. The best way to prevent him from losing his current job is to pop the login login dialog instead of jumping to the login page. This dialog box will need to listen for notauthenticated and sessiontimeout events, so when one of the events is triggered, the dialog box opens:
. directive (' Logindialog ', function (auth_events) {return
{
restrict: ' A ',
Template: ' <div ng-if= ' Visible "
ng-include=" \ login-form.html\ ' > ',
link:function (scope) {
var showdialog = function () {
scope.visible = true;
Scope.visible = false;
Scope. $on (auth_events.notauthenticated, ShowDialog);
Scope. $on (Auth_events.sessiontimeout, ShowDialog)
}}
;}
)
As long as you like, this dialog box can be arbitrarily extended. 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>
Pay attention to isloginpage inspection. A failed login will trigger notauthenticated time, but we do not want to display this dialog box on the landing page because it is superfluous and strange. That's why we don't put the login dialog on the landing page. So define a $scope in the Applicationcontroller. Isloginpage is reasonable.
Save User status
In the user refresh their page, still save the logged user information is a single page application certification inside a tricky link. Because the client is present in all States, the refresh clears the user information. To fix this problem, I usually implement an API (such as/profile) that returns data from the logged in current user, which is launched in the ANGULARJS application (for example, in the "Run" function). The user data is then saved in the session service or $rootScope, just as the user has logged in. Alternatively, you can embed the user data directly into the index.html so that no additional requests are made. The third way is to put the user's data in a cookie or localstorage, but this makes it harder to log out or empty the user's data.
At last......
My humble talents, a little experience, this is a translation of the article, if there is a fallacy, please correct me.
The above is the entire content of this article, I hope to help you learn, but also hope that we support the cloud habitat community.