Click here to Skip to main content
15,888,527 members
Articles / Web Development / IIS

Make ClickOnce Work With ASP.NET Forms Authentication

,
Rate me:
Please Sign up or sign in to vote.
4.36/5 (10 votes)
20 Mar 2008CPOL10 min read 139.2K   761   37  
A solution for securing access to a ClickOnce application using ASP.NET Forms authentication.
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)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions