Logowanie i kontrola dostępu użytkowników z JWT Bearer Token cz 2. (klient)

Dobry,

Dziś zabieramy się za drugą cześć logowania, a dokładniej za stronę klienta. Jeżeli ktoś z Was nie czytał poprzedniego wpisu to gorąco zachęcam do zapoznania się z nim, gdyż poruszyliśmy tam wiele istotnych kwestii 😉 Link macie o tu. Myślę, że bez zbędnych ceregieli możemy przejść do działania.

Pierwszą rzeczą jaką się zajmiemy jest klasa AuthService. Jej celem będzie zarządzanie pozyskanym z serwera access tokenem, oraz przetrzymywanie podstawowych informacji o użytkowniku tj. login oraz role. Implementacja wygląda następująco:

 

export interface IAuthService {
    user: IUser;
    accessToken: string;
    setUser(user: IUser): void;
    setAccessToken(accessToken: string, isSessionStored: boolean): void;
    getAccessToken(): string;
    clearAccessToken(): void
    isUserAdmin(): boolean;
}
 
export interface IUser {
    userName: string;
    roles: any[];
}
 
export class AuthService implements IAuthService {
 
    user: IUser;
    accessToken: string;
    private isSessionStored : boolean;
 
    constructor()
    {
        this.accessToken = '';
    }
 
    setUser(user: IUser) : void
    {
        this.user = user;
    }
 
    setAccessToken(accessToken: string, isSessionStored: boolean): void
    {
        if (isSessionStored)
        {
            localStorage.setItem('accessToken', accessToken);
            sessionStorage.clear();
        }
        else
        {
            sessionStorage.setItem('accessToken', accessToken);
            localStorage.clear();
        }
 
        this.accessToken = accessToken;
        this.isSessionStored = isSessionStored;
    }
 
    getAccessToken(): string
    {
        return this.isSessionStored? localStorage.getItem('accessToken') : sessionStorage.getItem('accessToken');
    }
 
    clearAccessToken(): void
    {
        localStorage.clear();
        sessionStorage.clear();
        document.cookie = '';
        this.setAccessToken('', true);
    }
 
    isUserAdmin(): boolean
    {
        if (!this.user) return false;
        return this.user.roles.some(r => r === 'Admin');
    }
}

 

Myślę, że kod nie wymaga dłuższego pochylenia się nad nim, bo za wiele się tu nie dzieje 😛 Chciałem tylko zwrócić naszą uwagę na metodę setAccessToken. Przyjmuje ona token oraz parametr isSessionStored, który informuje o tym, czy użytkownik wyraził chęć na przetrzymywanie jego „sesji”. Jeżeli wyraził, zapiszemy token do localStorage gdzie dane przetrzymywane są tak długo jak chce tego użytkownik. W przeciwnym przypadku token zapiszemy do sessionStorage, który po zamknięciu przeglądarki usunie z niego wszystkie dane.  Dobra, przejdźmy do view modelu, którego implementacja wygląda tak:

 


import auth = require("../../../auth-service")
import userServices = require("../services/user-service");
import models = require("../models/user-models");
import {inject} from 'aurelia-framework';
import {Router} from 'aurelia-router';
 
@inject(userServices.UserService, auth.AuthService, Router)
export class LoginViewModel
{
    userService: userServices.IUserService;
    authService: auth.IAuthService;
 
    userLoginDto: models.UserLoginDto;
 
    constructor(userService: userServices.UserService, authService: auth.AuthService, private router: Router)
    {
        this.userService = userService;
        this.authService = authService;
        this.userLoginDto = new models.UserLoginDto();
    }
 
    login()
    {
        this.userService.login(this.userLoginDto).then((result:string) => {
 
            this.authService.setAccessToken(result, this.userLoginDto.rememberMe);
 
            this.getUserSelfInfo().then(() =>
            {
                Materialize.toast(`welcome ${this.authService.user.userName}`, 4000, 'btn');
                this.router.navigate('#/');
            });
        });
    }
 
    getUserSelfInfo()
    {
        return this.userService.getUserSelfInfo().then((result: auth.IUser) =>
        {
            let user: auth.IUser = { userName: result.userName, roles: result.roles };
            this.authService.setUser(user);
        });
    }
}

 

Do naszego view modelu wstrzykujemy dwa serwisy: UserService oraz AuthService. Metoda login najpierw wykonuje żądanie poprzez userService. Kiedy serwer zwróci nam access token, zapiszemy go wcześniej omawianą metodą setAccessToken. Następnie metodą getUserSelfInfo wykonamy drugie zapytanie na serwer po dane użytkownika, które również zapiszemy w authService. Kiedy wszystko się powiedzie, witamy zalogowanego usera poprzez toastr i przekierowujemy go na stronę główną. Metody w serwisie wyglądają następująco:


login(userLoginDto: models.UserLoginDto): Promise<string>
   {
       return super.post('Accounts/Login', userLoginDto, false);
   }
 
   getUserSelfInfo() : Promise<auth.IUser>
   {
       return super.get('Accounts/SelfInfo', true);
   }

 

Warto zwrócić uwagę na to, że metoda getUserSelfInfo jest wywoływana z parametrem true, który oznacza że do żądania zostanie „doklejony” token. Cały serwis bazowy, który zajmuje się dołączaniem headera Authorization omówiłem we wpisie o rejestracji użytkowników.

Czas na widok:

 


<template>
    <div class="container">
        <div class="row">
            <form class="col s8 offset-s2 form-position ">
                <div class="row form-header">
                    <div class="col s2 offset-s4">
                       <h3>Login</h3>
                    </div>
                </div>
                <div class="row">
                    <div class="input-field col s6 offset-s3">
                        <input id="login-username" value.bind="userLoginDto.userName" type="text">
                        <label for="login-username">Username</label>
                    </div>
                </div>
                <div class="row">
                    <div class="input-field col s6 offset-s3">
                        <input id="login-password" value.bind="userLoginDto.password" type="password">
                        <label for="login-password">Password</label>
                    </div>
                </div>
                <div class="row">
                    <div class="input-field col s6 offset-s3">
                        <input value.checked="userLoginDto.rememberMe" type="checkbox">
                        <label>Remember me</label>
                    </div>
                </div>
                <div class="row">
                    <div class="col s6 offset-s3">
                        <a class="waves-effect waves-light btn" click.trigger="login()">Login!</a>
                    </div>
                </div>
            </form>
        </div>
    </div>
</template>

 

Jest to zwykły formularz, który posiada dwa pola tekstowe do wpisania nazwy użytkownika oraz hasła. Występuje tu także checkbox, którym użytkownik oznacza czy chce zostać zapamiętany. Ostatni element to guzik, który wywołuje metodę login w view modelu.

Myślę, że czas przetestować logowanie:

 

login

Kliknij, żeby odtworzyć !

 

Wygląda dobrze 🙂 Tu jeszcze mały screen, jak wygląda przetrzymywany w przeglądarce access token:

 

localstorage

 

Na dziś to wszystko! Mam nadzieje, że sumarycznie było to dla Was zrozumiałe. Jeśli jednak w trakcie czytania uznacie, że coś pominąłem, albo niezbyt jasno objaśniłem to dajcie znać w komentarzach, lub zerknijcie na githuba projektu. Zachęcam was również do śledzenia mojego twittera, żeby nie przegapiać przyszłych wpisów.

Trzymajcie się !

 

You may also like...