React autentizace » Historie » Revize 15
Revize 14 (Jiří Trefil, 2023-04-23 10:11) → Revize 15/17 (Jiří Trefil, 2023-04-23 10:17)
h1. Autentizace na straně klienta (React aplikace) h2. Popis logiky autentizace * Uživatel se přihlásí pod svým účtem. * Klientská aplikace pošle tento požadavek na SPADe aplikaci. Pokud je tento požadavek validní, tj. uživatel opravdu existuje a heslo je správné, pak je zpět odeslán JWT token (https://jwt.io/introduction). * Klientská aplikace si tento token přečte a uloží jej do cookie. Tento token je od tohoto bodu posílán s *každým* klientským požadavkem na server v hlavičce, viz obrázek hlavičky níže. !clipboard-202304231139-vx5qh.png! * Pouze takto je uživatel považován za autentikováno a může se svobodně pohybovat po aplikaci bez omezení. h1. Knihovna *react-auth-kit* * K implementaci této feature byla použita knihovna třetí strany. Knihovna spravuje celý stav autentizace na straně klienta. * Po přihlášení uživatele tedy knihovna uloží jwt token do cookie a následně poskytne 3 hooky (konkrétně useState), které s tímto tokenem manipulují. * Následuje ukázka kódu, která používá knihovnu pro přihlášení uživatele. h3. Přihlášení uživatele Ukázka kódu představuje první hook: *UseSignIn*. <pre><code class="react"> //importujeme useEffect knihovny pro prihlaseni uzivatele import {useSignIn} from "react-auth-kit"; //komponenta pro prihlaseni uzivatele const LoginComponent = () => { //initneme funkci knihovny //ktera udela vsechnu praci s ukladanim tokenu const signIn = useSignIn(); //dummy funkce, ktera pouze demonstruje, jak se knihovna pouziva //samotna implementace v kodu je v jadru stejna, pouze je kolem vice omacky //pro tuto ukazku nedulezite const sampleLogin = async (username:string, password: string) =>{ //V tomto bode zavolame server s nasim login pozadavkem //odpoved serveru je ve formatu jako nasledujici rozepsany objekt //tedy vratime nam status (HTTP code) - tim zjistime, jestli se neco nepokazilo //zpravu (message) - pro blizsi informace o vysledku informace //samotny jwt token (token) - timto tokenem se uzivatel autentikuje s kazdym nasledujicim pozadavkem na server const responseWrapper: {message:string; status:number; token:string} = await login(username,password); //token muze byt empty string, pokud doslo k chybe prihlaseni const accessToken:string = responseWrapper.token; if(responseWrapper.status != 200){ //Zde by byla cast, ktera by vypsala uzivateli chybovou hlasku, protoze se prihlaseni nepovedlo } //volame samotny useEffect pro ulozeni tokenu do cookie signIn({ //token, ktery ukladame, v nasem pripade promenna accessToken // cookie bude mit nazev "token" token:accessToken, //jak dlouho token zije (v minutach) expiresIn: 3600, //typ tokenu, v pripade JWT se jedna o Bearer token tokenType: "Bearer", // co dalsiho si chceme ulozit, napriklad jmeno uzivatele, nutne to tu byt nemusi authState: {userName:username} }); } } export default LoginComponent; </code></pre> h3. Kontrola, zda je uživatel autentikován Tato ukázka kódu představuje druhý hook: *useIsAuthenticated*. <pre><code class="react"> //naimportujeme si hook knihovny import {useIsAuthenticated} from "react-auth-kit"; import React, {useEffect, useState} from 'react'; import Nav from 'react-bootstrap/Nav'; import Navbar from 'react-bootstrap/Navbar'; import Container from 'react-bootstrap/Container'; //Komponenta reprezentuji navigacni menu pro uzivatele const NavBar = () => { //interni stav komponenty, ktera si drzi informace o tom, zda je uzivatel prihlasen const [isAuthenticated,setAuthenticated] = useState(false); //inicializujeme hook knihovny const authenticated = useIsAuthenticated(); //useEffect hook komponenty useEffect(()=>{ //pri kazde zmene stavu komponenty se dotazeme, zda je uzivatel autentikovan //token totiz uz mohl vyprset nebo samozrejme vubec nemusi existovat (uzivatel se jeste ani neprihlasil) //knihovna zde neudela nic vic, nez ze se podiva, jestli existuje cookie s nazvem "token", pokud existuje //tak se podiva, zda token neni expirovany (opet ma ulozeno v cookie, pouze pod jinym klicem) const isAuthed = authenticated(); //nastavime true/false podle toho, zda je uzivatel autentikovan, tj. ma platny a neexpirovany token setAuthenticated(isAuthed); }) return ( <Navbar bg="light" expand="lg"> <Container fluid> <Navbar.Brand href="/">React-Bootstrap</Navbar.Brand> <Navbar.Toggle aria-controls="basic-navbar-nav"/> <Navbar.Collapse id="basic-navbar-nav"> <Nav className="me-auto"> <Nav.Link href="/about">About</Nav.Link> <Nav.Link href="/detect">Detect</Nav.Link> <Nav.Link href="/configuration">Configuration</Nav.Link> </Nav> { //Je uzivatel prihlasen? //pokud je prihlasen, vykresli mu tlacitkou na odlhaseni //pokud neni prihlasen, vykresli mu tlacitko na prilaseni isAuthenticated? <Nav.Link href="/logout">Logout</Nav.Link> : <Nav.Link href="/login">Sign in</Nav.Link> } </Navbar.Collapse> </Container> </Navbar> ); }; export default NavBar; </code></pre> h3. Odhlášení uživatele Tato ukázka představí poslední hook: *useSignOut*. <pre><code class="react"> import React, { useEffect } from 'react'; //Naimportujeme si hooky. Nove tedy hook useSignOut, ktery vycisti cookies pri odhlaseni uzivatele import {useSignOut,useIsAuthenticated} from "react-auth-kit"; import {logoutUser} from "../api/APILogout"; import {retrieveUsernameFromStorage,invalidateLocalStorage} from "../context/LocalStorageManager"; function Logout() { //opet inicializujeme funkce knihovny //tedy funkce, ktera se pta, zda je uzivatel autentikovan const isAuthenticated = useIsAuthenticated(); // a funkce, ktera odhlasi uzivatele const signOut = useSignOut(); useEffect(() => { //vytahneme z local storage jmeno uzivatele, ktery se chce odhlasit const userName: string = retrieveUsernameFromStorage(); //pokud je uzivatel autentikovany a zaroven znam jeho jmeno, tak jej odhlasim if(isAuthenticated() && userName != null){ //zavolej server s requestem na odhlaseni, tedy znevalidneni tokenu logoutUser(userName); //smaz celou local storage uzivatele invalidateLocalStorage(); //samotne volani hooku knihovny - smaze vsechny cookies, ve kterych byl ulozen token a informace k tokenu signOut(); } //presmerujeme uzivatele zpatky na root stranku aplikace window.location.href = '/'; }); //vratime null, aby react router pochopil, ze se proklikem v navbaru vola tato funkce a nebude se vykreslovat komponenta return null; } export default Logout; </code></pre>