# MVC arhitektura Autentifikacija i autorizacija dio su ishoda učenja 3 (željeno). ## 12 Pregled - Autentifikacija i autorizacija: - https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-7.0#reacting-to-back-end-changes - https://learn.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?view=aspnetcore-7.0 ### 12.1 Postavljanje vježbe **Postavljanje SQL poslužitelja** U SQL Server Management Studiju učinite sljedeće: - preuzmite skriptu: https://pastebin.com/jtJfak9E - u skripti **promijenite naziv baze podataka u Exercise12 i koristite tu bazu** - izvršite je da biste stvorili bazu podataka, njezinu strukturu i neke testne podatke **Starter projekt** > Sljedeće je već dovršeno kao starter projekt: > > - Postavljeni modeli i repozitorij > - Podešen "Launch settings" > - Stvoreni osnovni CRUD prikazi i funkcionalnost (Genre, Artist, Song) > - Implementirana validacija i označavanje korištenjem viewmodela > > Za detalje pogledajte prethodne vježbe. Raspakirajte starter arhivu i otvorite rješenje u Visual Studiju. Postavite connection string i pokrenite aplikaciju. Provjerite radi li aplikacija (npr. navigacija, popis pjesama, dodavanje nove pjesme). > U slučaju da ne radi, provjerite jeste li ispravno slijedili upute. ### 12.2 Dodajte usluge za autentifikaciju pomoću kolačića i odgovarajući međuprogram Preduvjet za implementaciju MVC autentifikacije je dodavanje usluga i međuprograma za autentifikaciju putem kolačića. Autentifikacija putem kolačića način je provjere autentičnosti korisnika u Vašoj MVC aplikaciji. Izvršite korake iz poglavlja "Add cookie authentication" koje se nalazi na poveznici: - https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-7.0#add-cookie-authentication U detalje: - Dodajte uslugu autentifikacije - Dodajte autentifikacijski i autorizacijski međuprogram(autentifikacijski već postoji u predlošku projekta) - Označite `ArtistController` i `GenreController` atributom `[Authorize]` i pogledajte što se događa kada pokušate otvoriti te stranice u navigaciji. Pogledajte URL. > Možete promatrati URL preusmjeravanje koje se događa jer korisnik nije autentificiran. - Promijenite postavke međuprograma: ```C# builder.Services.AddAuthentication() .AddCookie(options => { options.LoginPath = "/User/Login"; options.LogoutPath = "/User/Logout"; options.AccessDeniedPath = "/User/Forbidden"; options.SlidingExpiration = true; options.ExpireTimeSpan = TimeSpan.FromMinutes(20); }) ``` - pogledajte što se sada događa kada kliknete na `Artists` u navigaciji - pogledajte URL > Sada se mijenja URL za preusmjeravanje jer smo dali prilagođene veze umjesto zadanih. Bilješke: - Ne trebate `app.MapRazorPages();` međuprogram jer ne koristimo "Razor stranice" - Kada međuprogram otkrije da treba preusmjeriti na prijavu, zadani LoginPath je `/Account/Login` - Kada međuprogram otkrije da treba preusmjeriti na odjavu, zadana putanja za odjavu je `/Account/Logout` - Kada međuprogram otkrije da treba preusmjeriti pristup zabranjenoj stranici, zadani AccessDeniedPath je `/Account/AccessDenied` - Ovisno o vašim krajnjim točkama autentifikacije, možete promijeniti ove zadane postavke, kao što ste i učinili - Pogledajte dokumentaciju za ostale postavke (`ExpireTimeSpan`, `SlidingExpiration`) ### 12.3 Stvorite autentifikacijski kolačić Ovaj se odjeljak temelji na sljedećem sadržaju: - https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-7.0#create-an-authentication-cookie Izvedite korake: - Stvorite prazan `UserController` - Stvorite akciju `Login` u kontroleru `UserController` i proslijedite joj parametar `string returnUrl` - Stvorite prazan `Login` prikaz i dodajte ovaj sadržaj: ```HTML

You have been logged in.

Go home ``` - Ažurirajte `_Layout.cshtml` kako biste uključili `Login` u navigaciju > Napomena: ova će akcija sada prihvatiti parametar `returnUrl` i preusmjeravanje međuprograma na stranicu koju ste upravo htjeli otvoriti, a to je npr. `/Artist/Index`. Ideja je sada upotrijebiti najjednostavniji mogući način za stvaranje kolačića za autentifikaciju kako biste mogli pristupiti zaštićenoj stranici (ovdje: `/Artist/Index`). Kasnije ćete to poboljšati koristeći stvarne vjerodajnice. Klikom na gumb `Login` u navigaciji, korisnik još nije prijavljen, samo se prikazuje poruka. Stvorite "prazan" autentifikacijski kolačić u akciji prijave: ```C# public IActionResult Login(string returnUrl) { var claims = new List(); var claimsIdentity = new ClaimsIdentity( claims, CookieAuthenticationDefaults.AuthenticationScheme); var authProperties = new AuthenticationProperties(); // We need to wrap async code here into synchronous since we don't use async methods Task.Run(async () => await HttpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties) ).GetAwaiter().GetResult(); if (returnUrl != null) return LocalRedirect(returnUrl); else return return RedirectToAction("Index", "Home"); } ``` Kada kliknete `Login`, akcija će stvoriti novi prazan kolačić koji će vam omogućiti pristup zaštićenim stranicama. Kada kliknete `Genres` ili `Artists`, međuprogram će proslijediti povratni URL akciji `Login`. Na kraju akcije, lokalno preusmjerite stranicu na taj URL. Otvorite "Development Tools" u pregledniku, odaberite karticu "Application" i pronađite kolačić. ### 12.4 Uklonite autentifikacijski kolačić Korisnik također mora biti u mogućnosti izvršiti odjavu. Implementirajte akciju `Logout` i predložak `Logout.cshtml`. - Implementirajte akciju: ```C# public IActionResult Logout() { Task.Run(async () => await HttpContext.SignOutAsync( CookieAuthenticationDefaults.AuthenticationScheme) ).GetAwaiter().GetResult(); return View(); } ``` - Dodajte predložak `Logout.cshtml`: ```HTML

You have been logged out.

Go home ``` - Ažurirajte `_Layout.cshtml` kako biste uključili gumb `Log Out` u navigaciji Sada kliknite gumb `Log Out`. Otvorite Development Tools u pregledniku, odaberite karticu "Application" i vidite da kolačića više nema. ### 12.5 Implementirajte obrazac za registraciju Implementacija obrasca za registraciju i prijavu radi se na isti način kao što ste to već učinili u Web API-ju, osim što su sada uključeni korisničko sučelje i kolačić. U Web API-ju ste umjesto toga imali Swagger i JWT. **Otvorite vježbu 6 za detalje.** Da budemo precizni, potrebno vam je sljedeće: - Viewmodeli za registraciju i prijavu - Koristite iste klase koje ste prije koristili kao DTO-ove, ali ih preimenujte, na primjer, iz `UserDTO` u `UserVM` - Isti "helper" `PasswordHashProvider` - Ne zaboravite mapu `Security` Sada podržite funkcionalnost `Register`: - Implementirajte prazne metode `GET Register()` i `POST Register()` kao dijelove kontrolera `UserController` - POST metoda prihvaća parametar `UserVM userVm` - Automatski generirajte prikaz `Register` (predložak `Create`, model `UserVM`) i fino podesite prikaz (uklonite ID, postavite ispravnu vrstu polja za lozinku...) - Dodajte poveznicu `Register` na layout prikaz - U POST metodi implementirajte isti algoritam za registraciju korisnika kao i za Web API (pronađite `POST Register` u **vježbi 6**) - Obratite pozornost na podršku za pristup bazi podataka u kontroleru - Na kraju algoritma nemojte vratiti `Ok()`, već preusmjerite na akciju `Index` kontrolera `Home` - Pazite da u slučaju greške iskoristite `ModelState.AddModelError()` i vratite `View()` Testirajte registraciju - provjerite postoje li podaci u bazi za registriranog korisnika. ### 12.6 Implementirajte obrazac za prijavu Sada podržite funkcionalnost `Login`: - Već imate metodu `GET Login()`, a potrebna vam je i metoda `POST Login()` - POST metoda prihvaća `LoginVM loginVm` parametar (kreirajte `LoginVM`, tamo vam trebaju samo korisničko ime i lozinka) - Premjestite logiku stvaranja kolačića na POST metodu - nema smisla stvarati kolačić prije nego što se korisnik stvarno prijavi - Morate podržati `returnUrl` u viewmodelu - Koristite skriveno polje u obrascu za održavanje podataka `returnUrl` dok se korisnik ne prijavi ``` public IActionResult Login(string returnUrl) { var loginVm = new LoginVM { ReturnUrl = returnUrl }; return View(); } ``` - Automatski generirajte prikaz `Login` (`Create` predložak, overwrite) preko postojećeg prikaza i fino podesite prikaz (postavite ispravnu vrstu za skrivena polja i polja za lozinku...) - Na početku POST metode sada implementirajte isti algoritam za pronalaženje i provjeru korisnika u bazi podataka kao i za Web API. S jednom razlikom - vrati View u slučaju greške i prije napuni grešku modela. ```C# // Try to get a user from database var existingUser = _context.Users.FirstOrDefault(x => x.Username == loginVm.Username); if (existingUser == null) { ModelState.AddModelError("", "Invalid username or password"); return View(); } // Check is password hash matches var b64hash = PasswordHashProvider.GetHash(loginVm.Password, existingUser.PwdSalt); if (b64hash != existingUser.PwdHash) { ModelState.AddModelError("", "Invalid username or password"); return View(); } ``` - Umjesto stvaranja i vraćanja JWT tokena, stvorite odgovarajući kolačić ```C# // Create proper cookie with claims var claims = new List() { new Claim(ClaimTypes.Name, loginVm.Username), new Claim(ClaimTypes.Role, "User") }; var claimsIdentity = new ClaimsIdentity( claims, CookieAuthenticationDefaults.AuthenticationScheme); var authProperties = new AuthenticationProperties(); Task.Run(async () => await HttpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties) ).GetAwaiter().GetResult(); ``` Testirajte i provjerite može li se registrirani korisnik sada prijaviti. ### 12.7 Prikažite korisniku podatke s kojima je prijavljen Za to morate zaviriti u `HttpContext`, isto kao što ste učinili u slučaju Web API-ja. Međutim, malo je drugačije ako to želite učiniti u layout prikazu (jer te informacije trebate kroz cijeli web site). U bilo kojem prikazu (također i `_Layout.cshtml`), `HttpContext` se može pronaći u `ViewContext`. Dodajte sljedeći kod nakon navigacije `