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.