I have the following structure:
- Project A: Duende Identity Server with additional features
- Project B: Web Client that uses Project A to authenticate
Both projects are using:
AuthorizeFilterto require authorized request by default.AutoValidateAntiforgeryTokenAttributeto require AntiforgeryToken by default.
Until now all the interaction works fine.
I need to create a dummy-authorized endpoint in Project B that calls an endpoint in Project A to create custom tokens.
In Project A my dummy endpoint is (the complete code):
[Route("ProjectAController/RequestToken")]
[HttpPost]
public async Task<IActionResult> RequestToken()
{
string antiForgeryInputValue = String.Empty;
string antiForgeryCookieValue = String.Empty;
string antiForgeryCookieKey = String.Empty;
var identityPath = _configuration["ProjectA_Link"];
bool getFromIdentityServer = true;
if (getFromIdentityServer)
{
using (var accountClient = _httpClientFactory.CreateClient("IDPClient2"))
{
var response = await accountClient.GetAsync($"{identityPath}/Account/Login");
var content = await response.Content.ReadAsStringAsync();
IEnumerable<string> cookies = response.Headers.SingleOrDefault(header => header.Key == "Set-Cookie").Value;
var rawAntiForgeryCookie = cookies.FirstOrDefault(c => c.StartsWith(".AspNetCore.Antiforgery"));
var match = Regex.Match(content, "name=\"__RequestVerificationToken\" type=\"hidden\" value=\"(.*?)\"");
if (match.Success)
antiForgeryInputValue = match.Groups[1].Value;
var cookieAnti = SetCookieHeaderValue.Parse(rawAntiForgeryCookie);
antiForgeryCookieValue = cookieAnti.Value.ToString();
antiForgeryCookieKey = cookieAnti.Name.ToString();
}
}
else
{
var antiForgeryHeaderTokenKey = Request.Form.FirstOrDefault(f => f.Key.StartsWith("__RequestVerificationToken")).Key;
antiForgeryInputValue = Request.Form[antiForgeryHeaderTokenKey].ToString();
antiForgeryCookieKey = Request.Cookies.Keys.FirstOrDefault(c => c.StartsWith(".AspNetCore.Antiforgery"));
antiForgeryCookieValue = Request.Cookies[antiForgeryCookieKey];
}
var cookieDommainBaseAddress = new Uri(_configuration["ProjectA_Link"]);
var container = HttpContext.RequestServices.GetService<CookieContainer>();
container.Add(cookieDommainBaseAddress, new Cookie("idsrv", Request.Cookies["idsrv"]));
container.Add(cookieDommainBaseAddress, new Cookie("idsrv.session", Request.Cookies["idsrv.session"]));
container.Add(cookieDommainBaseAddress, new Cookie(antiForgeryCookieKey, antiForgeryCookieValue));
using (var accountClient = _httpClientFactory.CreateClient("IDPClient2"))
{
var dataToPost = new List<KeyValuePair<string, string>>() { { new KeyValuePair<string, string>("__RequestVerificationToken", antiForgeryInputValue) } };
var content = new FormUrlEncodedContent(dataToPost);
var result = await accountClient.PostAsync($"{identityPath}/Authentication/GenerateToken", content);
result.EnsureSuccessStatusCode();
if (result.IsSuccessStatusCode)
{
var contentResult = await result.Content.ReadAsStringAsync();
var tokenGeneration = JsonSerializer.Deserialize<TokenGenerationResult>(contentResult, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
if (tokenGeneration != null)
return RedirectToAction("AuthStatus", new { referenceToken = tokenGeneration.ReferenceToken, refreshToken = tokenGeneration.RefreshToken });
}
}
return RedirectToAction("AuthStatus");
}
I'm trying to get the Antiforgery Cookie and Antiforgery token from ProjectA/Account/Login form, in this section:
if (getFromIdentityServer)
{
using (var accountClient = _httpClientFactory.CreateClient("IDPClient2"))
{
var response = await accountClient.GetAsync($"{identityPath}/Account/Login");
var content = await response.Content.ReadAsStringAsync();
IEnumerable<string> cookies = response.Headers.SingleOrDefault(header => header.Key == "Set-Cookie").Value;
var rawAntiForgeryCookie = cookies.FirstOrDefault(c => c.StartsWith(".AspNetCore.Antiforgery"));
var match = Regex.Match(content, "name=\"__RequestVerificationToken\" type=\"hidden\" value=\"(.*?)\"");
if (match.Success)
antiForgeryInputValue = match.Groups[1].Value;
var cookieAnti = SetCookieHeaderValue.Parse(rawAntiForgeryCookie);
antiForgeryCookieValue = cookieAnti.Value.ToString();
antiForgeryCookieKey = cookieAnti.Name.ToString();
}
}
To test other possibility I'm getting the Antiforgery Cookie and Antiforgery Token from the current request Project B, in this part:
else
{
var antiForgeryHeaderTokenKey = Request.Form.FirstOrDefault(f => f.Key.StartsWith("__RequestVerificationToken")).Key;
antiForgeryInputValue = Request.Form[antiForgeryHeaderTokenKey].ToString();
antiForgeryCookieKey = Request.Cookies.Keys.FirstOrDefault(c => c.StartsWith(".AspNetCore.Antiforgery"));
antiForgeryCookieValue = Request.Cookies[antiForgeryCookieKey];
}
In both cases I'm getting error on result.EnsureSuccessStatusCode();.
If get the antiforgery data from Project A login page the error is:
[23:55:39 Information] Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.AutoValidateAntiforgeryTokenAuthorizationFilter Antiforgery token validation failed. The provided antiforgery token was meant for a different claims-based user than the current user. Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException: The provided antiforgery token was meant for a different claims-based user than the current user. at Microsoft.AspNetCore.Antiforgery.DefaultAntiforgery.ValidateTokens(HttpContext httpContext, AntiforgeryTokenSet antiforgeryTokenSet) at Microsoft.AspNetCore.Antiforgery.DefaultAntiforgery.ValidateRequestAsync(HttpContext httpContext) at Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.ValidateAntiforgeryTokenAuthorizationFilter.OnAuthorizationAsync(AuthorizationFilterContext context)
If I try to get the Antiforgery data from Project B, current request the error is:
[00:33:27 Information] Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.AutoValidateAntiforgeryTokenAuthorizationFilter Antiforgery token validation failed. The antiforgery token could not be decrypted. Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException: The antiforgery token could not be decrypted. ---> System.Security.Cryptography.CryptographicException: The payload was invalid. For more information go to http://aka.ms/dataprotectionwarning at Microsoft.AspNetCore.DataProtection.Cng.CbcAuthenticatedEncryptor.DecryptImpl(Byte* pbCiphertext, UInt32 cbCiphertext, Byte* pbAdditionalAuthenticatedData, UInt32 cbAdditionalAuthenticatedData) at Microsoft.AspNetCore.DataProtection.Cng.Internal.CngAuthenticatedEncryptorBase.Decrypt(ArraySegment
1 ciphertext, ArraySegment1 additionalAuthenticatedData) at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.UnprotectCore(Byte[] protectedData, Boolean allowOperationsOnRevokedKeys, UnprotectStatus& status) at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.Unprotect(Byte[] protectedData) at Microsoft.AspNetCore.Antiforgery.DefaultAntiforgeryTokenSerializer.Deserialize(String serializedToken) --- End of inner exception stack trace --- at Microsoft.AspNetCore.Antiforgery.DefaultAntiforgeryTokenSerializer.Deserialize(String serializedToken) at Microsoft.AspNetCore.Antiforgery.DefaultAntiforgery.DeserializeTokens(HttpContext httpContext, AntiforgeryTokenSet antiforgeryTokenSet, AntiforgeryToken& cookieToken, AntiforgeryToken& requestToken) at Microsoft.AspNetCore.Antiforgery.DefaultAntiforgery.ValidateTokens(HttpContext httpContext, AntiforgeryTokenSet antiforgeryTokenSet) at Microsoft.AspNetCore.Antiforgery.DefaultAntiforgery.ValidateRequestAsync(HttpContext httpContext) at Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.ValidateAntiforgeryTokenAuthorizationFilter.OnAuthorizationAsync(AuthorizationFilterContext context)
One site can't decrypt another site's antiforgery token, because the token is encrypted using the Data Protection API. I describe this in my blog post here : https://nestenius.se/2023/11/22/exploring-what-is-inside-the-asp-net-core-cookies/
If both sites have the same Data Protection API encryption key, then both sites would be able to decrypt each other's tokens.
The Data Protection API depends on the token inside the form and the corresponding antiforgery cookie. Both must be present for it to work.
But the main question why bother with antiforgery at all when you want to do service to service communication? you could simply use some API key or some other way to protect the communication.