The AuthService class

This will be our first Angular service; we should be excited!

From Solution Explorer, navigate to the /ClientApp/app/ folder and create a /services/ subfolder within it. Right-click on the new folder and add an auth.service.ts TypeScript file; then, fill it with the following code (relevant lines are highlighted):

import { EventEmitter, Inject, Injectable, PLATFORM_ID } from "@angular/core";
import { isPlatformBrowser } from '@angular/common';
import { HttpClient, HttpHeaders } from "@angular/common/http";
import { Observable } from "rxjs";
import 'rxjs/Rx';

@Injectable()
export class AuthService {
authKey: string = "auth";
clientId: string = "TestMakerFree";

constructor(private http: HttpClient,
@Inject(PLATFORM_ID) private platformId: any) {
}

// performs the login
login(username: string, password: string): Observable<boolean> {
var url = "api/auth/jwt";
var data = {
username: username,
password: password,
client_id: clientId,
// required when signing up with username/password
grant_type: "password",
// space-separated list of scopes for which the token is
issued
scope: "offline_access profile email"
};

return this.http.post<TokenResponse>(url, data)
.map((res) => {
let token = res && res.token;
// if the token is there, login has been successful
if (token) {
// store username and jwt token
this.setAuth(res);
// successful login
return true;
}

// failed login
return Observable.throw('Unauthorized');
})
.catch(error => {
return new Observable<any>(error);
});
}
}

The highlighted lines do reveal the hot stuff here:

  • The isPlatformBrowser is an Angular function that, when feeded with an instance of the PLATFORM_ID token obtained through DI, returns true if the execution context represents a browser platform: this is something we'll need to know very soon.
  • The real magic happens within the login() method, which is in charge of issuing the HTTP POST request to the Auth method of our TokenController and performing the authentication using the username and password received by the caller. If the login happens, the relevant data (username and token) will be saved in the browser's localStorage--via the setAuth() method, which we'll see in a while--and the method will return true, otherwise it will throw an error that that will be handled by the caller accordingly.

Now that we understand how the login() method works, we can add the remaining logic. Append the following content below the previous code, just before the end of the class:

[...]

// performs the logout
logout(): boolean {
this.setAuth(null);
return true;
}

// Persist auth into localStorage or removes it if a NULL argument is given
setAuth(auth: TokenResponse | null): boolean {
if (isPlatformBrowser(this.platformId)) {
if (auth) {
localStorage.setItem(
this.authKey,
JSON.stringify(auth));
}
else {
localStorage.removeItem(this.authKey);
}
}
return true;
}

// Retrieves the auth JSON object (or NULL if none)
getAuth(): TokenResponse | null {
if (isPlatformBrowser(this.platformId)) {
var i = localStorage.getItem(this.authKey);
if (i) {
return JSON.parse(i);
}
}
return null;
}

// Returns TRUE if the user is logged in, FALSE otherwise.
isLoggedIn(): boolean {
if (isPlatformBrowser(this.platformId)) {
return localStorage.getItem(this.authKey) != null;
}
return false;
}

[...]

As we can see by looking at the preceding code, we added three methods that will actually access the localStorage: setAuth(), getAuth(), and isLoggedIn(). The first one is in charge of the insert, update, and delete operations; the second will retrieve the auth JSON object (if any); and the last one can be used to check whether the current user is authenticated or not without having to JSON.parse the entire object again. There's also a fourth method--logout()--which is basically a shortcut for setAuth(null).

It's worth noting how all these methods will only provide access to the localStorage if the executing context is a browser platform: this is what isPlatformBrowser and PLATFORM_ID are there for. This is just a safety net that prevents our code from creating potential issues with the server-side prerendering of our angular app performed by ASP.NET Core, which won't be able to deal properly with browser-specific features and browser types. This JavaScript coding approach is called isomorphic and it's a required pattern when developing an Angular Universal application, which is expected to run on client-side and server-side.

For more info about Angular Universal and isomorphic JavaScript check out the following resources:
https://universal.angular.io
http://isomorphic.net/javascript
https://github.com/angular/universal#angular-universal

For those who have never heard of it, the localStorage object is part of HTML5's Web Storage API; more specifically, it's a local caching object that keeps its content with no given expiration date. That's a great way to store our JWT-related JSON response, as we want to keep it even when the browser is closed. Before doing that, we choose to convert it into a string using JSON.stringify, since not all localStorage browser implementations can store JSON-type objects flawlessly. It's also worth mentioning that, instead of relying on the localStorage, we can use the sessionStorage object, that stores data only until the currently active session ends. However, that will mean deleting the token whenever the user closes the specific browser tab, which is hardly a desirable behavior for a SPA.

It's worth noting how all these methods will only provide access to the localStorage if the executing context is a browser platform: this is what isPlatformBrowser and PLATFORM_ID are there for. This is just a safety net that prevents our code from creating potential issues with the server-side prerendering of our angular app performed by ASP.NET Core, which won't be able to deal properly with browser-specific features and browser types. For more info about this specific topic, check out the Angular Universal paragraph in Chapter 9, Advanced Topics.

This quick localStorage implementation might be good for our sample SPA, but it could raise some performance issues in request-intensive scenarios as it gets the job done in a synchronous way, just like the localStorage API natively does. In case we want to adopt a better approach we can use the clever angular-async-local-storage NPM package by Cyrille Tuzi, which wraps the whole API into RxJS Observables to be homogeneous with the Angular way of doing things. For more info about this brilliant project, check out the following URL: https://www.npmjs.com/package/angular-async-local-storage

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.254.118