Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

Make ClickOnce Work With ASP.NET Forms Authentication

, , 20 Mar 2008
A solution for securing access to a ClickOnce application using ASP.NET Forms authentication.
clickoncesite_1.0.zip
ClickOnceSite
ClickOnceHandler
bin
My Project
Application.myapp
Settings.settings
obj
Debug
ClickOnceHandler.dll
TempPE
ClickOnceSite
App_Data
bin
ClickOnceApp
Application Files
XBAPFormsAuth_1_0_0_0
XBAPFormsAuth.exe.deploy
XBAPFormsAuth.exe.manifest
XBAPFormsAuth.xbap
XBAPRef.dll.deploy
XBAPFormsAuth.xbap
My Project
Application.myapp
Settings.settings
obj
Imports System
Imports System.Collections
Imports System.Configuration
Imports System.Security.Cryptography
Imports System.Security.Principal
Imports System.Text
Imports System.Web
Imports System.Security.Cryptography.X509Certificates
Imports Microsoft.Build.Tasks.Deployment.ManifestUtilities
Imports System.IO
Imports System.Web.Security

Public Class ClickOnceApplicationHandler
    Implements IHttpHandler

#Region " Configuration"

    Public Shared ReadOnly Property AppManifestName() As String
        Get
            Return GetConfigSetting("AppManifestName")
        End Get
    End Property

    Public Shared ReadOnly Property PathToManifest() As String
        Get
            Return GetConfigSetting("PathToManifest")
        End Get
    End Property

    Public Shared ReadOnly Property SiteRoot() As String
        Get
            Return GetConfigSetting("SiteRoot")
        End Get
    End Property

    Public Shared ReadOnly Property CertFilePath() As String
        Get
            Return GetConfigSetting("CertFilePath")
        End Get
    End Property

    Private Shared Function GetConfigSetting(ByVal pSettingName As String) As String
        Dim _val As String = System.Configuration.ConfigurationManager.AppSettings.Item(pSettingName)
        If _val Is Nothing Then
            Throw New Exception("Missing configuration setting: " & pSettingName)
        End If
        Return _val
    End Function
#End Region

    Public ReadOnly Property IsReusable() As Boolean Implements System.Web.IHttpHandler.IsReusable
        Get
            Return True
        End Get
    End Property

    ''' <summary>
    ''' Make a temp directory to hold the updated manifest while we sign it.
    ''' </summary>
    ''' <param name="pContext"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function MakeTempDir(ByVal pContext As HttpContext, ByVal pXbapFilePath As String) As String
        Dim _path As String = System.IO.Path.GetTempPath
        Dim _guid As Guid = Guid.NewGuid
        Dim _XbapFileName As String = Path.GetFileName(pXbapFilePath)

        _path = System.IO.Path.Combine(_path, _guid.ToString)
        System.IO.Directory.CreateDirectory(_path)
        System.IO.File.Copy(pXbapFilePath, System.IO.Path.Combine(_path, _XbapFileName))

        Return _path
    End Function

    ''' <summary>
    ''' Generate the xbap that contains the cookie-less authentication session information.
    ''' </summary>
    ''' <param name="pContext">The HttpContext for the request.</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function GenerateXbap(ByVal pContext As HttpContext, ByVal pXbapFilePath As String) As String
        Dim _path As String = MakeTempDir(pContext, pXbapFilePath)
        Dim _fpath As String = Path.Combine(_path, Path.GetFileName(pXbapFilePath))
        Dim fest As DeployManifest = CType(ManifestReader.ReadManifest(_fpath, False), DeployManifest)

        'This should really get pulled from the machine store, based on a thumbprint from the web.config
        SignAndSave(fest, CertFilePath, "", pContext)

        Return _fpath
    End Function

    ''' <summary>
    ''' Entry point for the handler, here we determine if we are dealing with the xbap, and modify its values and resign.
    ''' </summary>
    ''' <param name="context"></param>
    ''' <remarks></remarks>
    Public Sub ProcessRequest(ByVal context As System.Web.HttpContext) Implements System.Web.IHttpHandler.ProcessRequest
        Dim _path As String = context.Server.MapPath(context.Request.Path).ToLower

        If IO.File.Exists(_path) Then
            If _path.EndsWith(".xbap") Then
                'Set the correct mime type for xbap
                context.Response.ContentType = "application/x-ms-xbap"

                'Update the xbap with the cookieless authentication session information
                Dim _file As String = GenerateXbap(context, _path)

                'Write the updated file to the response
                context.Response.WriteFile(_file)
                context.Response.Flush()

                'Clean up the temporary file that has been generated.
                Directory.Delete(Path.GetDirectoryName(_file), True)
            End If
        Else
            Throw New HttpException(404, "File not found")
        End If
    End Sub

    Public Shared Sub SignAndSave(ByVal pDeploy As DeployManifest, ByVal pCertPath As String, ByVal pPassword As String, ByVal pContext As HttpContext)
        ' Update the deployment manifest
        UpdateDeployManifestAppReference(pDeploy, pContext)
        ' Write the deployment manifest 
        ManifestWriter.WriteManifest(pDeploy)
        ' Sign the deployment manifest 
        SignManifest(pDeploy, pCertPath, pPassword)
    End Sub

    Public Shared Sub SignManifest(ByVal manifest As Manifest, ByVal certFilePath As String, ByVal password As String)
        ' Make sure the entered cert file exists
        If (File.Exists(certFilePath)) Then
            ' Construct cert object for cert
            Dim cert As System.Security.Cryptography.X509Certificates.X509Certificate2
            If (String.IsNullOrEmpty(password)) Then
                cert = New System.Security.Cryptography.X509Certificates.X509Certificate2(certFilePath)
            Else
                cert = New System.Security.Cryptography.X509Certificates.X509Certificate2(certFilePath, password)
            End If
            SecurityUtilities.SignFile(cert, Nothing, manifest.SourcePath)
        Else
            Throw New ArgumentException("Invalid certificate file path")
        End If

    End Sub
    Public Shared Sub UpdateDeployManifestAppReference(ByVal depManifest As DeployManifest, ByVal pContext As HttpContext)
        Dim deployManifestPath As String = Path.GetDirectoryName(depManifest.SourcePath)

        'This should be the AssemblyReference for our main application manifest
        Dim _assemRef As AssemblyReference = depManifest.EntryPoint

        'Get the cookie value to use for the cookieless authentication.
        Dim _val As String = pContext.Request.Cookies.Item(FormsAuthentication.FormsCookieName).Value

        'Update the target path in the deployment manifest, this path will get reused for all the subsequent requests clickonce makes.
        _assemRef.TargetPath = String.Format("{0}(X(1)F({1})){2}{3}", SiteRoot, _val, PathToManifest, AppManifestName)
    End Sub
End Class

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Authors

David P Henry

United States United States
No Biography provided

Graham Murray

United States United States
No Biography provided

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 20 Mar 2008
Article Copyright 2008 by David P Henry, Graham Murray
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid