fix: authcontroller
This commit is contained in:
parent
b8ab7948a9
commit
c59e6711c0
@ -7,6 +7,9 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
using QRRapidoApp.Models.ViewModels;
|
using QRRapidoApp.Models.ViewModels;
|
||||||
using QRRapidoApp.Services;
|
using QRRapidoApp.Services;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
|
|
||||||
namespace QRRapidoApp.Controllers
|
namespace QRRapidoApp.Controllers
|
||||||
{
|
{
|
||||||
@ -16,14 +19,17 @@ namespace QRRapidoApp.Controllers
|
|||||||
private readonly AdDisplayService _adDisplayService;
|
private readonly AdDisplayService _adDisplayService;
|
||||||
private readonly ILogger<AccountController> _logger;
|
private readonly ILogger<AccountController> _logger;
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
|
private readonly IDataProtector _protector;
|
||||||
|
|
||||||
public AccountController(IUserService userService, AdDisplayService adDisplayService,
|
public AccountController(IUserService userService, AdDisplayService adDisplayService,
|
||||||
ILogger<AccountController> logger, IConfiguration configuration)
|
ILogger<AccountController> logger, IConfiguration configuration,
|
||||||
|
IDataProtectionProvider dataProtection)
|
||||||
{
|
{
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_adDisplayService = adDisplayService;
|
_adDisplayService = adDisplayService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
|
_protector = dataProtection.CreateProtector("OAuth.StateProtection");
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@ -38,11 +44,25 @@ namespace QRRapidoApp.Controllers
|
|||||||
public IActionResult LoginGoogle(string returnUrl = "/")
|
public IActionResult LoginGoogle(string returnUrl = "/")
|
||||||
{
|
{
|
||||||
var baseUrl = _configuration.GetSection("App:BaseUrl").Value;
|
var baseUrl = _configuration.GetSection("App:BaseUrl").Value;
|
||||||
|
|
||||||
|
// Criar state com dados criptografados em vez de sessão
|
||||||
|
var stateData = new OAuthStateData
|
||||||
|
{
|
||||||
|
ReturnUrl = returnUrl,
|
||||||
|
Nonce = Guid.NewGuid().ToString(),
|
||||||
|
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||||
|
};
|
||||||
|
|
||||||
|
var stateJson = JsonSerializer.Serialize(stateData);
|
||||||
|
var protectedState = _protector.Protect(stateJson);
|
||||||
|
var encodedState = Convert.ToBase64String(Encoding.UTF8.GetBytes(protectedState));
|
||||||
|
|
||||||
var properties = new AuthenticationProperties
|
var properties = new AuthenticationProperties
|
||||||
{
|
{
|
||||||
RedirectUri = $"{baseUrl}{Url.Action("GoogleCallback")}",
|
RedirectUri = $"{baseUrl}{Url.Action("GoogleCallback")}",
|
||||||
Items = { { "returnUrl", returnUrl } }
|
Items = { { "state", encodedState } }
|
||||||
};
|
};
|
||||||
|
|
||||||
return Challenge(properties, GoogleDefaults.AuthenticationScheme);
|
return Challenge(properties, GoogleDefaults.AuthenticationScheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,40 +70,84 @@ namespace QRRapidoApp.Controllers
|
|||||||
public IActionResult LoginMicrosoft(string returnUrl = "/")
|
public IActionResult LoginMicrosoft(string returnUrl = "/")
|
||||||
{
|
{
|
||||||
var baseUrl = _configuration.GetSection("App:BaseUrl").Value;
|
var baseUrl = _configuration.GetSection("App:BaseUrl").Value;
|
||||||
//var redirectUrl = Url.Action("MicrosoftCallback", "Account", new { returnUrl });
|
|
||||||
var redirectUrl = "";
|
|
||||||
|
|
||||||
if (returnUrl == "/")
|
// Mesmo processo para Microsoft
|
||||||
|
var stateData = new OAuthStateData
|
||||||
{
|
{
|
||||||
redirectUrl = $"{baseUrl}/Account/MicrosoftCallback";
|
ReturnUrl = returnUrl,
|
||||||
}
|
Nonce = Guid.NewGuid().ToString(),
|
||||||
|
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||||
|
};
|
||||||
|
|
||||||
|
var stateJson = JsonSerializer.Serialize(stateData);
|
||||||
|
var protectedState = _protector.Protect(stateJson);
|
||||||
|
var encodedState = Convert.ToBase64String(Encoding.UTF8.GetBytes(protectedState));
|
||||||
|
|
||||||
|
var redirectUrl = returnUrl == "/"
|
||||||
|
? $"{baseUrl}/Account/MicrosoftCallback"
|
||||||
|
: $"{baseUrl}/Account/MicrosoftCallback";
|
||||||
|
|
||||||
|
var properties = new AuthenticationProperties
|
||||||
|
{
|
||||||
|
RedirectUri = redirectUrl,
|
||||||
|
Items = { { "state", encodedState } }
|
||||||
|
};
|
||||||
|
|
||||||
var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
|
|
||||||
return Challenge(properties, MicrosoftAccountDefaults.AuthenticationScheme);
|
return Challenge(properties, MicrosoftAccountDefaults.AuthenticationScheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> GoogleCallback()
|
public async Task<IActionResult> GoogleCallback(string state = null)
|
||||||
{
|
{
|
||||||
return await HandleExternalLoginCallbackAsync(GoogleDefaults.AuthenticationScheme);
|
var returnUrl = await HandleExternalLoginCallbackAsync(GoogleDefaults.AuthenticationScheme, state);
|
||||||
|
return Redirect(returnUrl ?? "/");
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> MicrosoftCallback()
|
public async Task<IActionResult> MicrosoftCallback(string state = null)
|
||||||
{
|
{
|
||||||
return await HandleExternalLoginCallbackAsync(MicrosoftAccountDefaults.AuthenticationScheme);
|
var returnUrl = await HandleExternalLoginCallbackAsync(MicrosoftAccountDefaults.AuthenticationScheme, state);
|
||||||
|
return Redirect(returnUrl ?? "/");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IActionResult> HandleExternalLoginCallbackAsync(string scheme)
|
private async Task<string> HandleExternalLoginCallbackAsync(string scheme, string state = null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_adDisplayService.SetViewBagAds(ViewBag);
|
_adDisplayService.SetViewBagAds(ViewBag);
|
||||||
|
|
||||||
|
// Recuperar returnUrl do state em vez da sessão
|
||||||
|
string returnUrl = "/";
|
||||||
|
if (!string.IsNullOrEmpty(state))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var decodedState = Encoding.UTF8.GetString(Convert.FromBase64String(state));
|
||||||
|
var unprotectedState = _protector.Unprotect(decodedState);
|
||||||
|
var stateData = JsonSerializer.Deserialize<OAuthStateData>(unprotectedState);
|
||||||
|
|
||||||
|
// Validar timestamp (não mais que 10 minutos)
|
||||||
|
var maxAge = DateTimeOffset.UtcNow.ToUnixTimeSeconds() - (10 * 60);
|
||||||
|
if (stateData.Timestamp > maxAge)
|
||||||
|
{
|
||||||
|
returnUrl = stateData.ReturnUrl ?? "/";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning($"OAuth state expired for scheme {scheme}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, $"Failed to decode OAuth state for scheme {scheme}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var result = await HttpContext.AuthenticateAsync(scheme);
|
var result = await HttpContext.AuthenticateAsync(scheme);
|
||||||
if (!result.Succeeded)
|
if (!result.Succeeded)
|
||||||
{
|
{
|
||||||
_logger.LogWarning($"External authentication failed for scheme {scheme}");
|
_logger.LogWarning($"External authentication failed for scheme {scheme}");
|
||||||
return RedirectToAction("Login");
|
return "/Account/Login";
|
||||||
}
|
}
|
||||||
|
|
||||||
var email = result.Principal?.FindFirst(ClaimTypes.Email)?.Value;
|
var email = result.Principal?.FindFirst(ClaimTypes.Email)?.Value;
|
||||||
@ -93,7 +157,7 @@ namespace QRRapidoApp.Controllers
|
|||||||
if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(providerId))
|
if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(providerId))
|
||||||
{
|
{
|
||||||
_logger.LogWarning($"Missing required claims from {scheme} authentication");
|
_logger.LogWarning($"Missing required claims from {scheme} authentication");
|
||||||
return RedirectToAction("Login");
|
return "/Account/Login";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find or create user
|
// Find or create user
|
||||||
@ -127,13 +191,12 @@ namespace QRRapidoApp.Controllers
|
|||||||
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
|
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
|
||||||
new ClaimsPrincipal(claimsIdentity), authProperties);
|
new ClaimsPrincipal(claimsIdentity), authProperties);
|
||||||
|
|
||||||
var returnUrl = result.Properties?.Items != null && result.Properties.Items.ContainsKey("returnUrl") ? result.Properties?.Items["returnUrl"] : "/";
|
return returnUrl;
|
||||||
return RedirectToAction("Index", "Home");
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, $"Error in external login callback for {scheme}");
|
_logger.LogError(ex, $"Error in external login callback for {scheme}");
|
||||||
return RedirectToAction("Login");
|
return "/Account/Login";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -252,4 +315,12 @@ namespace QRRapidoApp.Controllers
|
|||||||
return Json(new { success = false });
|
return Json(new { success = false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Classe para dados do state
|
||||||
|
public class OAuthStateData
|
||||||
|
{
|
||||||
|
public string ReturnUrl { get; set; } = "/";
|
||||||
|
public string Nonce { get; set; } = "";
|
||||||
|
public long Timestamp { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user