Skip to content

Commit 81a7fd5

Browse files
committed
#280 - Refresh Token na App MVC - feature/sp8/#280
1 parent 6527f69 commit 81a7fd5

File tree

8 files changed

+147
-54
lines changed

8 files changed

+147
-54
lines changed

src/building blocks/JSE.WebAPI.Core/User/AspNetUser.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ public string ObterUserToken()
2929
return EstaAutenticado() ? _accessor.HttpContext.User.GetUserToken() : "";
3030
}
3131

32+
public string ObterUserRefreshToken()
33+
{
34+
return EstaAutenticado() ? _accessor.HttpContext.User.GetUserRefreshToken() : "";
35+
}
36+
3237
public bool EstaAutenticado()
3338
{
3439
return _accessor.HttpContext.User.Identity.IsAuthenticated;

src/building blocks/JSE.WebAPI.Core/User/ClaimsPrincipalExtensions.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,17 @@ public static string GetUserToken(this ClaimsPrincipal principal)
3636
var claim = principal.FindFirst("JWT");
3737
return claim?.Value;
3838
}
39+
40+
public static string GetUserRefreshToken(this ClaimsPrincipal principal)
41+
{
42+
if (principal == null)
43+
{
44+
throw new ArgumentException(nameof(principal));
45+
}
46+
47+
var claim = principal.FindFirst("RefreshToken");
48+
return claim?.Value;
49+
}
50+
3951
}
4052
}

src/building blocks/JSE.WebAPI.Core/User/IAspNetUser.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public interface IAspNetUser
99
Guid ObterUserId();
1010
string ObterUserEmail();
1111
string ObterUserToken();
12+
string ObterUserRefreshToken();
1213
bool EstaAutenticado();
1314
bool PossuiRole(string role);
1415
IEnumerable<Claim> ObterClaims();
Lines changed: 17 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
1-
using JSE.WebApp.MVC.Models;
1+
using JSE.WebApp.MVC.Controllers;
2+
using JSE.WebApp.MVC.Models;
23
using JSE.WebApp.MVC.Services;
3-
using Microsoft.AspNetCore.Authentication;
4-
using Microsoft.AspNetCore.Authentication.Cookies;
54
using Microsoft.AspNetCore.Mvc;
6-
using System.IdentityModel.Tokens.Jwt;
7-
using System.Security.Claims;
85

9-
namespace JSE.WebApp.MVC.Controllers
6+
namespace NSE.WebApp.MVC.Controllers
107
{
118
public class IdentidadeController : MainController
129
{
13-
1410
private readonly IAutenticacaoService _autenticacaoService;
1511

16-
public IdentidadeController(IAutenticacaoService autenticacaoService)
12+
public IdentidadeController(
13+
IAutenticacaoService autenticacaoService)
1714
{
1815
_autenticacaoService = autenticacaoService;
1916
}
@@ -27,18 +24,17 @@ public IActionResult Registro()
2724

2825
[HttpPost]
2926
[Route("nova-conta")]
30-
public async Task<IActionResult> Registro(UsuarioRegistroViewModel usuarioRegistroViewModel)
27+
public async Task<IActionResult> Registro(UsuarioRegistroViewModel usuarioRegistro)
3128
{
32-
if (!ModelState.IsValid) return View(usuarioRegistroViewModel);
29+
if (!ModelState.IsValid) return View(usuarioRegistro);
3330

34-
var resposta = await _autenticacaoService.Registro(usuarioRegistroViewModel);
31+
var resposta = await _autenticacaoService.Registro(usuarioRegistro);
3532

36-
if (ResponsePossuiErros(resposta.ResponseResult)) return View(usuarioRegistroViewModel);
33+
if (ResponsePossuiErros(resposta.ResponseResult)) return View(usuarioRegistro);
3734

38-
await RealizarLogin(resposta);
35+
await _autenticacaoService.RealizarLogin(resposta);
3936

4037
return RedirectToAction("Index", "Catalogo");
41-
4238
}
4339

4440
[HttpGet]
@@ -51,17 +47,16 @@ public IActionResult Login(string returnUrl = null)
5147

5248
[HttpPost]
5349
[Route("login")]
54-
public async Task<IActionResult> Login(UsuarioLoginViewModel usuarioLoginViewModel, string returnUrl = null)
50+
public async Task<IActionResult> Login(UsuarioLoginViewModel usuarioLogin, string returnUrl = null)
5551
{
5652
ViewData["ReturnUrl"] = returnUrl;
53+
if (!ModelState.IsValid) return View(usuarioLogin);
5754

58-
if (!ModelState.IsValid) return View(usuarioLoginViewModel);
55+
var resposta = await _autenticacaoService.Login(usuarioLogin);
5956

60-
var resposta = await _autenticacaoService.Login(usuarioLoginViewModel);
57+
if (ResponsePossuiErros(resposta.ResponseResult)) return View(usuarioLogin);
6158

62-
if (ResponsePossuiErros(resposta.ResponseResult)) return View(usuarioLoginViewModel);
63-
64-
await RealizarLogin(resposta);
59+
await _autenticacaoService.RealizarLogin(resposta);
6560

6661
if (string.IsNullOrEmpty(returnUrl)) return RedirectToAction("Index", "Catalogo");
6762

@@ -72,35 +67,8 @@ public async Task<IActionResult> Login(UsuarioLoginViewModel usuarioLoginViewMod
7267
[Route("sair")]
7368
public async Task<IActionResult> Logout()
7469
{
75-
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
70+
await _autenticacaoService.Logout();
7671
return RedirectToAction("Index", "Catalogo");
7772
}
78-
79-
private async Task RealizarLogin(UsuarioRespostaLoginViewModel usuarioRespostaLoginViewModel)
80-
{
81-
var token = ObterTokenFormatado(usuarioRespostaLoginViewModel.AccessToken);
82-
83-
var claims = new List<Claim>();
84-
claims.Add(new Claim("JWT", usuarioRespostaLoginViewModel.AccessToken));
85-
claims.AddRange(token.Claims);
86-
87-
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
88-
89-
var authProperties = new AuthenticationProperties
90-
{
91-
ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(60),
92-
IsPersistent = true
93-
};
94-
95-
await HttpContext.SignInAsync(
96-
CookieAuthenticationDefaults.AuthenticationScheme,
97-
new ClaimsPrincipal(claimsIdentity),
98-
authProperties);
99-
}
100-
101-
private static JwtSecurityToken ObterTokenFormatado(string jwtToken)
102-
{
103-
return new JwtSecurityTokenHandler().ReadToken(jwtToken) as JwtSecurityToken;
104-
}
10573
}
106-
}
74+
}

src/web/JSE.WebApp.MVC/Extensions/ExceptionMiddleware.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Polly.CircuitBreaker;
1+
using JSE.WebApp.MVC.Services;
2+
using Polly.CircuitBreaker;
23
using Refit;
34
using System.Net;
45

@@ -7,14 +8,17 @@ namespace JSE.WebApp.MVC.Extensions
78
public class ExceptionMiddleware
89
{
910
private readonly RequestDelegate _next;
11+
private static IAutenticacaoService _autenticacaoService;
1012

1113
public ExceptionMiddleware(RequestDelegate next)
1214
{
1315
_next = next;
1416
}
1517

16-
public async Task InvokeAsync(HttpContext httpContext)
18+
public async Task InvokeAsync(HttpContext httpContext, IAutenticacaoService autenticacaoService)
1719
{
20+
_autenticacaoService = autenticacaoService;
21+
1822
try
1923
{
2024
await _next(httpContext);
@@ -41,6 +45,17 @@ private static void HandleRequestExceptionAsync(HttpContext context, HttpStatusC
4145
{
4246
if (statusCode == HttpStatusCode.Unauthorized)
4347
{
48+
if(_autenticacaoService.TokenExpirado())
49+
{
50+
if(_autenticacaoService.RefreshTokenValido().Result)
51+
{
52+
context.Response.Redirect(context.Request.Path);
53+
return;
54+
}
55+
}
56+
57+
_autenticacaoService.Logout();
58+
4459
context.Response.Redirect($"/login?ReturnUrl={context.Request.Path}");
4560
return;
4661
}

src/web/JSE.WebApp.MVC/Models/UsuarioRespostaLoginViewModel.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace JSE.WebApp.MVC.Models
55
public class UsuarioRespostaLoginViewModel
66
{
77
public string AccessToken { get; set; }
8+
public string RefreshToken { get; set; }
89
public double ExpiresIn { get; set; }
910
public UsuarioTokenViewModel UsuarioToken { get; set; }
1011
public ResponseResult ResponseResult { get; set; }

src/web/JSE.WebApp.MVC/Services/AutenticacaoService.cs

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,32 @@
11
using JSE.Core.Communication;
2+
using JSE.WebAPI.Core.User;
23
using JSE.WebApp.MVC.Extensions;
34
using JSE.WebApp.MVC.Models;
5+
using Microsoft.AspNetCore.Authentication;
6+
using Microsoft.AspNetCore.Authentication.Cookies;
47
using Microsoft.Extensions.Options;
8+
using System.IdentityModel.Tokens.Jwt;
9+
using System.Security.Claims;
510

611
namespace JSE.WebApp.MVC.Services
712
{
813
public class AutenticacaoService : Service, IAutenticacaoService
914
{
1015
private readonly HttpClient _httpClient;
16+
private readonly IAuthenticationService _authenticationService;
17+
private readonly IAspNetUser _user;
18+
1119

1220
public AutenticacaoService(HttpClient httpClient,
13-
IOptions<AppSettings> settings)
21+
IOptions<AppSettings> settings,
22+
IAuthenticationService authenticationService,
23+
IAspNetUser user)
1424
{
1525
httpClient.BaseAddress = new Uri(settings.Value.AutenticacaoUrl);
1626

17-
_httpClient = httpClient;
27+
_httpClient = httpClient;
28+
_authenticationService = authenticationService;
29+
_user = user;
1830
}
1931

2032
public async Task<UsuarioRespostaLoginViewModel> Login(UsuarioLoginViewModel usuarioLogin)
@@ -50,5 +62,81 @@ public async Task<UsuarioRespostaLoginViewModel> Registro(UsuarioRegistroViewMod
5062

5163
return await DeserializarObjetoResponse<UsuarioRespostaLoginViewModel>(response);
5264
}
65+
66+
public async Task RealizarLogin(UsuarioRespostaLoginViewModel resposta)
67+
{
68+
var token = ObterTokenFormatado(resposta.AccessToken);
69+
70+
var claims = new List<Claim>();
71+
claims.Add(new Claim("JWT", resposta.AccessToken));
72+
claims.Add(new Claim("RefreshToken", resposta.RefreshToken));
73+
claims.AddRange(token.Claims);
74+
75+
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
76+
77+
var authProperties = new AuthenticationProperties
78+
{
79+
ExpiresUtc = DateTimeOffset.UtcNow.AddHours(8),
80+
IsPersistent = true
81+
};
82+
83+
await _authenticationService.SignInAsync(
84+
_user.ObterHttpContext(),
85+
CookieAuthenticationDefaults.AuthenticationScheme,
86+
new ClaimsPrincipal(claimsIdentity),
87+
authProperties);
88+
}
89+
90+
public async Task Logout()
91+
{
92+
await _authenticationService.SignOutAsync(
93+
_user.ObterHttpContext(),
94+
CookieAuthenticationDefaults.AuthenticationScheme,
95+
null);
96+
}
97+
98+
public static JwtSecurityToken ObterTokenFormatado(string jwtToken)
99+
{
100+
return new JwtSecurityTokenHandler().ReadToken(jwtToken) as JwtSecurityToken;
101+
}
102+
103+
public async Task<UsuarioRespostaLoginViewModel> UtilizarRefreshToken(string refreshToken)
104+
{
105+
var refreshTokenContent = ObterConteudo(refreshToken);
106+
107+
var response = await _httpClient.PostAsync("/api/identidade/refresh-token", refreshTokenContent);
108+
109+
if (!TratarErrosResponse(response))
110+
{
111+
return new UsuarioRespostaLoginViewModel
112+
{
113+
ResponseResult = await DeserializarObjetoResponse<ResponseResult>(response)
114+
};
115+
}
116+
117+
return await DeserializarObjetoResponse<UsuarioRespostaLoginViewModel>(response);
118+
}
119+
120+
public bool TokenExpirado()
121+
{
122+
var jwt = _user.ObterUserToken();
123+
if (jwt is null) return false;
124+
125+
var token = ObterTokenFormatado(jwt);
126+
return token.ValidTo.ToLocalTime() < DateTime.Now;
127+
}
128+
129+
public async Task<bool> RefreshTokenValido()
130+
{
131+
var resposta = await UtilizarRefreshToken(_user.ObterUserRefreshToken());
132+
133+
if (resposta.AccessToken != null && resposta.ResponseResult == null)
134+
{
135+
await RealizarLogin(resposta);
136+
return true;
137+
}
138+
139+
return false;
140+
}
53141
}
54142
}

src/web/JSE.WebApp.MVC/Services/IAutenticacaoService.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ namespace JSE.WebApp.MVC.Services
55
public interface IAutenticacaoService
66
{
77
Task<UsuarioRespostaLoginViewModel> Login(UsuarioLoginViewModel usuarioLogin);
8-
98
Task<UsuarioRespostaLoginViewModel> Registro(UsuarioRegistroViewModel usuarioRegistro);
9+
Task RealizarLogin(UsuarioRespostaLoginViewModel resposta);
10+
Task Logout();
11+
bool TokenExpirado();
12+
Task<bool> RefreshTokenValido();
1013
}
1114
}

0 commit comments

Comments
 (0)