profileShare

rasmusjy / splitapp-backend-microservices

Read-only snapshot

No repository description.

main default branch 501 files Expires Sep 13, 2026, 9:06 AM
AccountController.cs 3,970 bytes
1 using System.Security.Claims;
2 using Microsoft.AspNetCore.Authorization;
3 using Microsoft.AspNetCore.Mvc;
4 using SplitApp.WebApp.Application.UsersService;
5 using SplitApp.WebApp.Models.Account;
6
7 namespace SplitApp.WebApp.Controllers;
8
9 /// <summary>
10 /// MVC entry-point for login / register / logout / profile. Bridges the browser to the
11 /// Users service over HTTP and turns the JWT into an HttpOnly cookie so MVC views work
12 /// without manual Authorization headers. SameSite=Lax so the post→redirect→get login flow
13 /// works (Strict would drop the cookie on the redirect).
14 /// </summary>
15 public class AccountController : Controller
16 {
17 private readonly IUsersServiceClient _users;
18
19 public AccountController(IUsersServiceClient users) => _users = users;
20
21 [HttpGet]
22 public IActionResult Login(string? returnUrl = null)
23 {
24 return View(new LoginViewModel { ReturnUrl = returnUrl });
25 }
26
27 [HttpPost]
28 [ValidateAntiForgeryToken]
29 public async Task<IActionResult> Login(LoginViewModel vm, CancellationToken ct)
30 {
31 if (!ModelState.IsValid) return View(vm);
32
33 var result = await _users.LoginAsync(vm.Email, vm.Password, ct);
34 if (!result.Success || result.Value is null)
35 {
36 ModelState.AddModelError(string.Empty, result.Error ?? "Login failed.");
37 return View(vm);
38 }
39
40 SetAuthCookies(result.Value.Jwt, result.Value.RefreshToken);
41 return SafeRedirect(vm.ReturnUrl);
42 }
43
44 [HttpGet]
45 public IActionResult Register(string? returnUrl = null)
46 {
47 return View(new RegisterViewModel { ReturnUrl = returnUrl });
48 }
49
50 [HttpPost]
51 [ValidateAntiForgeryToken]
52 public async Task<IActionResult> Register(RegisterViewModel vm, CancellationToken ct)
53 {
54 if (!ModelState.IsValid) return View(vm);
55
56 var result = await _users.RegisterAsync(vm.Email, vm.Password, vm.FirstName, vm.LastName, ct);
57 if (!result.Success || result.Value is null)
58 {
59 ModelState.AddModelError(string.Empty, result.Error ?? "Registration failed.");
60 return View(vm);
61 }
62
63 SetAuthCookies(result.Value.Jwt, result.Value.RefreshToken);
64 return SafeRedirect(vm.ReturnUrl);
65 }
66
67 [HttpPost]
68 [ValidateAntiForgeryToken]
69 [Authorize]
70 public async Task<IActionResult> Logout(CancellationToken ct)
71 {
72 var refresh = Request.Cookies["refresh"];
73 if (!string.IsNullOrEmpty(refresh))
74 {
75 await _users.LogoutAsync(refresh, ct);
76 }
77 Response.Cookies.Delete("jwt");
78 Response.Cookies.Delete("refresh");
79 return RedirectToAction("Index", "Home");
80 }
81
82 [HttpGet]
83 [Authorize]
84 public IActionResult Manage()
85 {
86 var vm = new ManageViewModel
87 {
88 Email = User.FindFirstValue(ClaimTypes.Email) ?? "",
89 FirstName = User.FindFirstValue(ClaimTypes.GivenName) ?? "",
90 LastName = User.FindFirstValue(ClaimTypes.Surname) ?? "",
91 };
92 return View(vm);
93 }
94
95 private void SetAuthCookies(string jwt, string refreshToken)
96 {
97 var jwtOpts = new CookieOptions
98 {
99 HttpOnly = true,
100 Secure = Request.IsHttps,
101 SameSite = SameSiteMode.Lax,
102 Expires = DateTimeOffset.UtcNow.AddHours(1),
103 };
104 Response.Cookies.Append("jwt", jwt, jwtOpts);
105
106 var refreshOpts = new CookieOptions
107 {
108 HttpOnly = true,
109 Secure = Request.IsHttps,
110 SameSite = SameSiteMode.Lax,
111 Expires = DateTimeOffset.UtcNow.AddDays(7),
112 };
113 Response.Cookies.Append("refresh", refreshToken, refreshOpts);
114 }
115
116 private IActionResult SafeRedirect(string? returnUrl)
117 {
118 if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))
119 {
120 return Redirect(returnUrl);
121 }
122 return RedirectToAction("Index", "Home");
123 }
124 }
125