2008-02-28

ClickOnce, SharePoint and Anonymous access

I have been laborating with a ClickOnce deployed application in SharePoint and all seems to work well until I tried to connect to SharePoint (wss3) from a workstation where I used another locally logged in user than the one I used to authenticate myself at the SharePoint server.

When I then tried to access the ClickOnce application the bootstrap downloader (ApplicationActivator) prompts a "Cannot Start Application" error that says "Cannot retrieve application. Authentication error".

The details of the error indicates a 401 response from the webserver.
System.Deployment.Application.DeploymentDownloadException (Unknown subtype)
- Downloading http://wm20031/_layouts/MyApp/MyApp.application did not succeed.
- Source: System.Deployment
- Stack trace:
at System.Deployment.Application.SystemNetDownloader.DownloadSingleFile(DownloadQueueItem next)
at System.Deployment.Application.SystemNetDownloader.DownloadAllFiles()
at System.Deployment.Application.FileDownloader.Download(SubscriptionState subState)
at System.Deployment.Application.DownloadManager.DownloadManifestAsRawFile(Uri& sourceUri, String targetPath, IDownloadNotification notification, DownloadOptions options, ServerInformation& serverInformation)
at System.Deployment.Application.DownloadManager.DownloadDeploymentManifestDirectBypass(SubscriptionStore subStore, Uri& sourceUri, TempFile& tempFile, SubscriptionState& subState, IDownloadNotification notification, DownloadOptions options, ServerInformation& serverInformation)
at System.Deployment.Application.DownloadManager.DownloadDeploymentManifestBypass(SubscriptionStore subStore, Uri& sourceUri, TempFile& tempFile, SubscriptionState& subState, IDownloadNotification notification, DownloadOptions options)
at System.Deployment.Application.ApplicationActivator.PerformDeploymentActivation(Uri activationUri, Boolean isShortcut, String textualSubId, String deploymentProviderUrlFromExtension)
at System.Deployment.Application.ApplicationActivator.ActivateDeploymentWorker(Object state)

--- Inner Exception ---
System.Net.WebException
- The remote server returned an error: (401) Unauthorized.
- Source: System
- Stack trace:
at System.Net.HttpWebRequest.GetResponse()
at System.Deployment.Application.SystemNetDownloader.DownloadSingleFile(DownloadQueueItem next)

If I check the weblog we can se the following two entries

2008-02-28 12:05:29 W3SVC1265274740 192.168.1.199 GET /_layouts/MyApp/MyApp.application 80 domain\administrator 192.168.1.2 Mozilla/4.0+(...) 206 0 0
2008-02-28 12:05:29 W3SVC1265274740 192.168.1.199 GET /_layouts/MyApp/MyApp.application 80 - 192.168.1.2 - 401 5 0
The first is the webbrowser access where I authenticate using the domain\administrator account and the application manifest is successfully returned to the browser. Then the ApplicationActivator tries the same thing as anonymous but fails utterly. Ok, so perhaps this is only anonymous user access that is denied, but I had already checked the "Enable Anonymous access" in the IIS manager for the _layouts/MyApp folder.

Testing with firefox and anonymous access proved that is not the access rights that is incorrectly set.
2008-02-28 12:16:56 W3SVC1265274740 192.168.1.199 GET /_layouts/MyApp/MyApp.application 80 - 192.168.1.2 Mozilla/5.0+... 200 0 0
So the IIS is not blocking access, then it has to be SharePoint? (_layouts is a SharePoint managed folder)

To test I checked the code for the System.Deployment.Application.SystemNetDownloader.DownloadSingleFile(DownloadQueueItem next) method and reproduced the code that was processed there.

WebRequest request = WebRequest.Create("http://wm20031/_layouts/MyApp/MyApp.application");
request.Credentials = CredentialCache.DefaultCredentials;
RequestCachePolicy policy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
request.CachePolicy = policy;
HttpWebRequest request2 = request as HttpWebRequest;
if (request2 != null) {
request2.UnsafeAuthenticatedConnectionSharing = true;
request2.AutomaticDecompression = DecompressionMethods.GZip;
request2.CookieContainer = GetUriCookieContainer(request2.RequestUri);
WebRequest.DefaultWebProxy.Credentials = CredentialCache.DefaultCredentials;
}
WebResponse response = null;
response = request.GetResponse();
using (StreamReader reader = new StreamReader(response.GetResponseStream())) {
Console.WriteLine(reader.ReadToEnd());
}
response.Close();
This code fails with the same exception as the Application Activator and after some debugging I noticed that if I didn't copy the cookies to the request, then it worked?!
Checking further in the cookie container, it only added one cookie MSOWebPartPage_AnonymousAccessCookie and with the value of the webapplication port. (80)

The GetUriCookieContainer parsed the cookies retrieved in IE for the url. If I removed the cookie for the server in the temporary internet files folder, the code above worked even with the cookie row (no cookies added).

To solve the problem one workaround is to create a virtual folder in the root of the SharePoint site (MyApp) (that is not managed by SharePoint), map this to the same folder as _layouts/MyApp, allow anonymous access there and navigate to that url instead (/MyApp/MyApp.application). This works since the cookie is not present for the application.

But what I really like to know is the purpose of the MSOWebPartPage_AnonymousAccessCookie, especially why SharePoint sets this cookie when I navigate to the site and why it throws an access denied when I try to access with the cookie set?