
By
Shah Chandon. September 26, 2024.
In .Net 8, the on-premise WS-Fed authentication has changed slightly. As a result the .Net 6 version of the WS-Fed authentication is no longer working.
This is based of off: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/2406#issuecomment-1814246256
All credits are due to the user “Hakon” in the above thread.
.Net 6 version of authentication
In the program.cs/startup.cs we have the following code that enables the ws-fed authentication:
// Add AD Authentication
builder.Services.AddAuthentication(
sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
})
.AddWsFederation(options =>
{
options.Wtrealm = builder.Configuration["wsfed:realm"];
options.MetadataAddress = builder.Configuration["wsfed:metadata"];
})
.AddCookie();
In the appsettings.json we have the following config for ws-fed:
"wsfed": {
"realm": "https://localhost:44342",
"metadata": "https://adfstest.uwaterloo.ca/federationmetadata/2007-06/federationmetadata.xml"
}
Error with .Net 6 WS-Fed
Below is the sample of the error that was received when trying to access the site with the .Net 6 version of the code.
An unhandled exception occurred while processing the request.
XmIReadException: IDX30011: Unable to read XML. Expecting XmIReader to be at ns.element: '[PII of
type 'System.StringI is hidden. For more details, see https://aka.ms/IdentityModel/PIl.].[PlI of type
'System.String' is hidden. For more details, see https://aka.ms/IdentityModeI/Pll.]', found: '[PII of type
'System.String' is hidden. For more details, see https://aka.ms/IdentityModeI/Pll.].[PlI of type
'System.String' is hidden. For more details, see https://aka.ms/IdentityModeI/Pll.]'.
Microsoft.ldentityModel.Xml.XmlUtil.CheckReaderOnEntry(XmlReader reader, string element, string namespace)
AggregateException: One or more errors occurred. (IDX30011: Unable to read XML. Expecting
XmIReader to be at ns.element: '[PII of type 'System.String' is hidden. For more details, see
https://aka.ms/IdentityModel/PIl.].[PlI of type 'System.String' is hidden. For more details, see
https://aka.ms/IdentityModel/PIl.]I, found: '[PII of type 'System.StringI is hidden. For more details, see
https://aka.ms/IdentityModel/PIl.].[PlI of type 'System.String' is hidden. For more details, see
https://aka.ms/IdentityModel/PIl.]I.) (IDX10204: Unable to validate issuer.
validationParameters.Validlssuer is null or whitespace AND validationParameters.VaIidIssuers is null or
empty.) (IDX14122: JWT is not a well formed JWE, there are more than four dots (.) a JWE can have at
most 4 dots.
The token needs to be in JWS or JWE Compact Serialization Format. (JWS):
'EncodedHeader.EncodedPayIoad.EncodedSignature'. (JWE):
'EncodedProtectedHeader.EncodedEncryptedKey.EncodedlnitiaIizationVector.EncodedCiphertext.Enco
Microsoft.ldentityModel.Xml.XmlUtil.CheckReaderOnEntry(XmlReader reader, string element, string namespace)
SecurityTokenException: No token validator or token handler was found for the given token.
Microsoft.As NetCore.Authentication.WsFederation.WsFederationHandler.HandleRemoteAuthenticateAs nc
Cause
It seems some of the validationParameters are received from the security server but not populated as validationParameters. The values are kept inside validation configuration!
Add a new security token handler
We need to add a new security token handler that will read the validation configuration and then populate the missing vallidation parameter properties.
using Microsoft.IdentityModel.Tokens.Saml;
using Microsoft.IdentityModel.Tokens;
namespace myAccessLostNFound.CustomSecurityHandlers
{
/// <summary>
/// This class helps with the validation of the WSFed tokens. Some of the validation params are empty in .net 8.
/// However, those values exists in the configurationManager of the validationParam itself at runtime.
/// This method, reads those values from the runtime config manager and adds them directly under the validationParameters object.
/// This is based of off: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/2406#issuecomment-1814246256
/// </summary>
public class CustomSamlSecurityTokenHandler : SamlSecurityTokenHandler
{
public override async Task<TokenValidationResult> ValidateTokenAsync(string token, TokenValidationParameters validationParameters)
{
var configuration = await validationParameters.ConfigurationManager.GetBaseConfigurationAsync(CancellationToken.None).ConfigureAwait(false);
var issuers = new[] { configuration.Issuer };
validationParameters.ValidIssuers = (validationParameters.ValidIssuers == null ? issuers : validationParameters.ValidIssuers.Concat(issuers));
validationParameters.IssuerSigningKeys = (validationParameters.IssuerSigningKeys == null ? configuration.SigningKeys : validationParameters.IssuerSigningKeys.Concat(configuration.SigningKeys));
var baseValidationRes = await base.ValidateTokenAsync(token, validationParameters);
return baseValidationRes;
}
}
}
Register the token handler in the program.cs/startup.cs
Clear out any previous token handlers and then add the new one. The new handler calls the base token validation method after populating the validation params.
// Add AD Authentication
builder.Services.AddAuthentication(
sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
})
.AddWsFederation(options =>
{
options.Wtrealm = builder.Configuration["wsfed:realm"];
options.MetadataAddress = builder.Configuration["wsfed:metadata"];
options.TokenHandlers.Clear();
options.TokenHandlers.Add(new CustomSamlSecurityTokenHandler());
})
.AddCookie();
Comments/thoughts
Please feel free to let me know.
