sec: use HMAC to encrypt state
This commit is contained in:
parent
08aeb7ea3c
commit
598ebabc5c
@ -61,7 +61,7 @@ public static class DependencyInjection
|
||||
providers.Add(provider, (clientId, secret));
|
||||
}
|
||||
|
||||
services.AddSingleton(provider => new OAuthService(provider.GetRequiredService<ILogger<OAuthService>>(), providers));
|
||||
services.AddSingleton(provider => new OAuthService(provider.GetRequiredService<ILogger<OAuthService>>(), providers, configuration["SECURITY_ENCRYPTION_TOKEN"]!));
|
||||
|
||||
return services;
|
||||
}
|
||||
|
@ -10,13 +10,15 @@ using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Security;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Mirea.Api.Security.Services;
|
||||
|
||||
public class OAuthService(ILogger<OAuthService> logger, Dictionary<OAuthProvider, (string ClientId, string Secret)> providers)
|
||||
public class OAuthService(ILogger<OAuthService> logger, Dictionary<OAuthProvider, (string ClientId, string Secret)> providers, string secretKey)
|
||||
{
|
||||
private static readonly Dictionary<OAuthProvider, OAuthProviderUrisData> ProviderData = new()
|
||||
{
|
||||
@ -97,6 +99,12 @@ public class OAuthService(ILogger<OAuthService> logger, Dictionary<OAuthProvider
|
||||
return userInfo?.MapToInternalUser();
|
||||
}
|
||||
|
||||
private static string GetHmacString(RequestContextInfo contextInfo, string secretKey)
|
||||
{
|
||||
var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secretKey));
|
||||
return Convert.ToBase64String(hmac.ComputeHash(
|
||||
Encoding.UTF8.GetBytes($"{contextInfo.Fingerprint}_{contextInfo.Ip}_{contextInfo.UserAgent}")));
|
||||
}
|
||||
|
||||
public Uri GetProviderRedirect(HttpContext context, CookieOptionsParameters cookieOptions, string redirectUri, OAuthProvider provider)
|
||||
{
|
||||
@ -106,7 +114,7 @@ public class OAuthService(ILogger<OAuthService> logger, Dictionary<OAuthProvider
|
||||
"&response_type=code" +
|
||||
$"&redirect_uri={redirectUri}" +
|
||||
$"&scope={ProviderData[provider].Scope}" +
|
||||
$"&state={new RequestContextInfo(context, cookieOptions).Fingerprint}_{Enum.GetName(provider)}";
|
||||
$"&state={GetHmacString(new RequestContextInfo(context, cookieOptions), secretKey)}_{Enum.GetName(provider)}";
|
||||
|
||||
|
||||
|
||||
@ -121,8 +129,6 @@ public class OAuthService(ILogger<OAuthService> logger, Dictionary<OAuthProvider
|
||||
|
||||
public async Task<(OAuthProvider provider, OAuthUser User)> LoginOAuth(HttpContext context, CookieOptionsParameters cookieOptions, string redirectUrl, string code, string state, CancellationToken cancellation = default)
|
||||
{
|
||||
var requestContext = new RequestContextInfo(context, cookieOptions);
|
||||
|
||||
var partsState = state.Split('_');
|
||||
|
||||
if (!Enum.TryParse<OAuthProvider>(partsState.Last(), true, out var provider) ||
|
||||
@ -133,9 +139,10 @@ public class OAuthService(ILogger<OAuthService> logger, Dictionary<OAuthProvider
|
||||
throw new InvalidOperationException("Invalid authorization request.");
|
||||
}
|
||||
|
||||
var fingerprint = string.Join("_", partsState.SkipLast(1));
|
||||
var secretStateData = string.Join("_", partsState.SkipLast(1));
|
||||
var secretData = GetHmacString(new RequestContextInfo(context, cookieOptions), secretKey);
|
||||
|
||||
if (requestContext.Fingerprint != fingerprint)
|
||||
if (secretData != secretStateData)
|
||||
{
|
||||
logger.LogWarning("Fingerprint mismatch. Possible CSRF attack detected.");
|
||||
throw new SecurityException("Suspicious activity detected. Please try again.");
|
||||
|
Loading…
x
Reference in New Issue
Block a user