Invalid Base64 characters? Invalid XML?
Anyone that has had to work with these FedAuth tokens would have experienced errors with the format of the FedAuth tokens. My initial response always is – how is this posssible?
This is Microsoft code, I don’t encode or decode the tokens how can something be wrong.
Well… the bottom line is – something goes wrong when the cookies get too big and they need to get chunked into usual 2K pieces. Cookies have a size limit and that is why chunking happens.
These are some of the exception messages:
- System.FormatException – Invalid length for a Base-64 char array or string.
- System.Xml.XmlException – The data at the root level is invalid. Line 1, position 1.
The stack traces always show that the fault is in the SessionAuthenticationModule which is outside our control.
The Solution
How to get around this? Best solution is to implement Reference tokens. Reference tokens stores the bulk of the cookie on the server and only the reference is stored in the FedAuth cookie. This prevents the cookies from getting so big that the cookie gets chunked but even that is not good enough.
It is still possible for tokens to get corrupted and that results in the application crashing with an unhandled exception because the exception occurs in the Microsoft module that is outside our control. To catch these exception the best solution is to implement your own SessionAuthenticationModule in which you catch these exceptions and raise an exception of your own choice.
In the sample below I will show
- How to derive from SessionAuthenticationModule and catch the exceptions.
- How to integrate your own module in the web.config file.
- How to listen for exceptions in the Global.ascx
Overview
In the code below I derive my own module and catch the exceptions. I ten raise a SecurityTokenException which I then listen for and catch in the Global.ascx.
Once the exception has been caught in the Global.ascx I accept that I cannot parse the token and that it is unrecoverable. The best solution is to now delete the token and redirect back to yourself which will cause the Federation logic to kick in and redirect the user back to the Identity provider (STS) to be authentication.
The source
using System;
using System.Collections.Generic;
using System.IdentityModel.Services;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
namespace Company.Application.HttpModules
{
public class YourModule : SessionAuthenticationModule
{
//
// Summary:
// Handles the System.Web.HttpApplication.AuthenticateRequest event from the ASP.NET
// pipeline.
//
// Parameters:
// sender:
// The source for the event. This will be an System.Web.HttpApplication object.
//
// eventArgs:
// The data for the event.
//
// Exceptions:
// T:System.InvalidOperationException:
// There is not a valid session token handler configured. (There is no System.IdentityModel.Tokens.SessionSecurityTokenHandler
// configured in the System.IdentityModel.Configuration.IdentityConfiguration.SecurityTokenHandlers
// property.)
protected override void OnAuthenticateRequest(object sender, EventArgs eventArgs)
{
try
{
base.OnAuthenticateRequest(sender, eventArgs);
}
catch (XmlException ex)
{
throw new SecurityTokenException(ex.Message, ex);
}
catch (System.FormatException ex)
{
throw new SecurityTokenException(ex.Message, ex);
}
}
//
// Summary:
// Handles the System.Web.HttpApplication.PostAuthenticateRequest event from the
// ASP.NET pipeline.
//
// Parameters:
// sender:
// The source for the event. This will be an System.Web.HttpApplication object.
//
// e:
// The data for the event.
protected override void OnPostAuthenticateRequest(object sender, EventArgs e)
{
try
{
base.OnPostAuthenticateRequest(sender, e);
}
catch (Exception)
{
throw;
}
}
//
// Summary:
// Raises the System.IdentityModel.Services.SessionAuthenticationModule.SessionSecurityTokenCreated
// event.
//
// Parameters:
// args:
// The data for the event.
protected override void OnSessionSecurityTokenCreated(SessionSecurityTokenCreatedEventArgs args)
{
try
{
base.OnSessionSecurityTokenCreated(args);
}
catch (Exception)
{
throw;
}
}
//
// Summary:
// Raises the System.IdentityModel.Services.SessionAuthenticationModule.SessionSecurityTokenReceived
// event.
//
// Parameters:
// args:
// The data for the event.
protected override void OnSessionSecurityTokenReceived(SessionSecurityTokenReceivedEventArgs args)
{
try
{
base.OnSessionSecurityTokenReceived(args);
}
catch (Exception)
{
throw;
}
}
//
// Summary:
// Raises the System.IdentityModel.Services.SessionAuthenticationModule.SignedOut
// event.
//
// Parameters:
// e:
// The data for the event.
protected override void OnSignedOut(EventArgs e)
{
try
{
base.OnSignedOut(e);
}
catch (Exception)
{
throw;
}
}
//
// Summary:
// Raises the System.IdentityModel.Services.SessionAuthenticationModule.SigningOut
// event.
//
// Parameters:
// e:
// The data for the event.
protected override void OnSigningOut(SigningOutEventArgs e)
{
try
{
base.OnSignedOut(e);
}
catch (Exception)
{
throw;
}
}
//
// Summary:
// Raises the System.IdentityModel.Services.SessionAuthenticationModule.SignOutError
// event.
//
// Parameters:
// e:
// The data for the event.
protected override void OnSignOutError(ErrorEventArgs e)
{
try
{
base.OnSignOutError(e);
}
catch (Exception)
{
throw;
}
}
//
// Summary:
// Sets the principal on the System.Web.HttpContext and System.Threading.Thread
// to the principal that is contained in the specified session token.
//
// Parameters:
// sessionSecurityToken:
// The session token from which to set the principal.
protected override void SetPrincipalFromSessionToken(SessionSecurityToken sessionSecurityToken)
{
try
{
base.SetPrincipalFromSessionToken(sessionSecurityToken);
}
catch (Exception)
{
throw;
}
}
}
}
Web.config integration snippet.
<modules runAllManagedModulesForAllRequests="true"> <!-- Note the SessionAuthenticationModule must be declared above the WSFederationAuthenticationModule otherwise the session does not get logged out as the events does get wired up --> <add name="YourModule " type="Company.Application.HttpModules.YourModule " preCondition="managedHandler" /> <add name="WSFederationAuthenticationModule" type="System.IdentityModel.Services.WSFederationAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" /> </modules>
Global.ascx snippet.
///
///
///
///
void Application_Error(object sender, EventArgs e)
{
// Check for available exception.
Exception exception = Server.GetLastError();
if (exception == null)
return;
if (exception is SecurityTokenException)
{
if (FederatedAuthentication.SessionAuthenticationModule != null)
{
FederatedAuthentication.SessionAuthenticationModule.DeleteSessionTokenCookie();
Response.Redirect(“/”);
}}
}