Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Silverlight RIA Services - How To Best Handle Client Auth Session Timeout?

I built an app with Silverlight4, RIA Services, and I'm using ASP.NET Membership for authentication/authorization.

My web.config has this:

<system.web>
 <sessionState timeout="20"/>
 <authentication mode="Forms">
  <forms name="_ASPXAUTH" timeout="20"/>
 </authentication>

I have read a number of different strategies on how to deal with auth/session timeout on the client side. That is: if the client is idle for x minutes (20 here), and then they do something with the UI that triggers a RIA/WCF call, I want to trap on that event and deal with appropriately (e.g. take them back to the login screen) -- in a nutshell: I need a way to differentiate from a bona-fide server side DomainException vs. an auth failure because the session timed out.

AFAIK: there is no typed exception or property that can determine this. The only way I've been able to determine this -- which seems like a hack: is to inspect the Error's Message string and look for something like "Access denied" or "denied". For example: something like this:

if (ex.Message.Contains("denied"))
  // this is probably an auth failure b/c of a session timeout

So, this is what I'm currently doing, and it works if I run and debug either with the built-in server from VS2010, or if I run in localhost IIS. If I set the timeout to 1 minute, login, wait more than a minute and trigger another call, I breakpoint on the exception and enter the if code block above and all is well.

Then I deploy the app to a remote IIS7 server and I try the same test and it doesn't work. So, I added log tracing, and here's the event where the exception happened:

<E2ETraceEvent xmlns="http://schemas.microsoft.com/2004/06/E2ETraceEvent">
 <System xmlns="http://schemas.microsoft.com/2004/06/windows/eventlog/system">
  <EventID>131076</EventID>
  <Type>3</Type>
  <SubType Name="Error">0</SubType>
  <Level>2</Level>
  <TimeCreated SystemTime="2011-10-30T22:13:54.6425781Z" />
  <Source Name="System.ServiceModel" />
  <Correlation ActivityID="{20c26991-372f-430f-913b-1b72a261863d}" />
  <Execution ProcessName="w3wp" ProcessID="4316" ThreadID="24" />
  <Channel />
  <Computer>TESTPROD-HOST</Computer>
 </System>
 <ApplicationData>
  <TraceData>
   <DataItem>
    <TraceRecord xmlns="http://schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord" Severity="Error">
     <TraceIdentifier>http://msdn.microsoft.com/en-US/library/System.ServiceModel.Diagnostics.TraceHandledException.aspx</TraceIdentifier>
     <Description>Handling an exception.</Description>
     <AppDomain>/LM/W3SVC/1/ROOT/sla-2-129644844652558594</AppDomain>
     <Exception>
      <ExceptionType>System.ServiceModel.FaultException`1[[System.ServiceModel.DomainServices.Hosting.DomainServiceFault, System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]], System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType>
      <Message></Message>
       <StackTrace>
        at System.ServiceModel.DomainServices.Hosting.QueryOperationBehavior`1.QueryOperationInvoker.InvokeCore(Object instance, Object[] inputs, Object[]&amp; outputs)
        at System.ServiceModel.DomainServices.Hosting.DomainOperationInvoker.Invoke(Object instance, Object[] inputs, Object[]&amp; outputs)
        at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc&amp; rpc)
        at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc&amp; rpc)
        at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc&amp; rpc)
        at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)
     </StackTrace>
     <ExceptionString>System.ServiceModel.FaultException`1[System.ServiceModel.DomainServices.Hosting.DomainServiceFault]:  (Fault Detail is equal to System.ServiceModel.DomainServices.Hosting.DomainServiceFault).</ExceptionString>
  </Exception>
 </TraceRecord>
</DataItem>
</TraceData>
</ApplicationData>
</E2ETraceEvent>

The problem is that I don't have the string in the error message that indicates "denied" or "Access denied" - and I am unsure as to why this solution works in localhost IIS or VS2010 host but not in a remote IIS7 server. Is there some obscure configuration setting that I'm missing here? Is there a better way to do this in general?

like image 669
zenocon Avatar asked Oct 30 '11 23:10

zenocon


1 Answers

You've probably gotten by this by now, but this article describes using the DomainOperationException and checking the error codes.

dex.ErrorCode == ErrorCodes.NotAuthenticated || dex.ErrorCode == ErrorCodes.Unauthorized

For convenient access (and in case the we loose access to the blog) here's the blog article by Josh Eastburn:

A question that comes up often from developers who are working with Silverlight and WCF RIA Services: why does my Silverlight application throw an exception when it has been idle for a period of time? As you might expect, it is due to the authenticated session timing out. But it isn’t quite that straightforward. Because Silverlight uses a client/server architecture, the client can operate independent of the server for an indefinite period of time. It is only when the Silverlight client makes a call to the server that the server-side timeout is realized. There are a few options to handle the client-server timeout issue (and you may be able to come up with a few more): If you aren’t concerned with the security implications of removing a session timeout, you can either increase the timeout setting in web.config, or create a DispatcherTimer in the Silverlight client that calls a simple method on the server to act as a "Keep Alive." Add a DispatcherTimer to the Silverlight client that stays in sync with the server-side timeout and warn/prompt the user keep the session active before the time expires or have them re-authenticate if it has already expired. However, this requires extra effort to keep the timers in sync when new server requests are made. Allow the server to handle the timeout as it normally would and handle the timeout gracefully on the Silverlight client. This means that the timeout is determined by server call activity, NOT activity confined the Silverlight client (i.e. accessing client-side data in the context). Of these three options, I find the third to be the best balance of security and usability while at the same time not adding unnecessary complexity to the application. In order to handle these server-side timeouts globally, you can add the following logic in either the Application_UnhandledException method in App.xaml.cs or in your global ViewModel loading construct if you have one:

 // Check for Server-Side Session Timeout Exception
 var dex = e.ExceptionObject as DomainOperationException; 
 if ((dex != null) && (dex.ErrorCode == ErrorCodes.NotAuthenticated || dex.ErrorCode == ErrorCodes.Unauthorized) && WebContext.Current.User.IsAuthenticated) 
 {
    // A server-side timeout has occurred.  Call LoadUser which will automatically
    //   authenticate if "Remember Me" was checked, or prompt for the user to log on again
    WebContext.Current.Authentication.LoadUser(Application_UserLoaded, null);
    e.Handled = true; 
 }

The following constants are defined within the ErrorCodes class:

public static class ErrorCodes 
{
     public const int NotAuthenticated = 0xA01;
     public const int Unauthorized = 401; 
}  

When the server-side session times out, any subsequent calls will return a DomainOperationException. By inspecting the returned ErrorCode, you can determine if it is an authentication error and handle it accordingly. In my example, I am calling WebContext.Current.Authentication.LoadUser() which will attempt to re-authenticate the user if possible. Even if the user can not be automatically re-authenticated, it will call back to my Application_UserLoaded method. There I can check WebContext.Current.User.IsAuthenticated to determine whether to proceed with the previous operation or if I need to redirect back to the home page and reprompt for login. Here is an example of some code in the Appliation_UserLoaded callback that shows a login dialog if the user is not authenticated:

// Determine if the user is authenticated
if (!WebContext.Current.User.IsAuthenticated) 
{
    // Show login dialog automatically
    LoginRegistrationWindow loginWindow = new LoginRegistrationWindow();
    loginWindow.Show(); 
}   

To test your code, you can set your timeout value in web.config to a small value so timeouts occur quickly:

<authentication mode="Forms">   
     <forms name=".Falafel_ASPXAUTH" timeout="1" /> 
</authentication>   

If you’d like to see all of this code in a working solution, check out our Silverlight RIA Template on CodePlex.

like image 162
AlignedDev Avatar answered Nov 15 '22 20:11

AlignedDev