Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  1. Call the ServiceValidate method from the main (Proxier) application for CAS authentication and include a pgtUrl attribute.
    Note
    titleAbout the pgtUrl

    The pgtUrl MUST be part of the same application as the CAS Proxier AND be on a secure server with a valid RSA or Verisign SSL Certificate

  2. The pgtUrl receives back from CAS the pgtIou/pgtId pair and stores them in an Application variable (e.g. Application(<value of pgtIou>) = <value of pgtId>)
    Info
    titleThe use of Application variables

    Application variables MUST be used instead of Session variables. This is because of the extra trip CAS has to make to the pgtUrl. Your application would lose it's session variables otherwise. This is NOT true in basic CAS Authentication so, you will notice through out these examples that I chose to use Application variables instead of Session variables for this reason. Also, note the use of the name of the Application variable being the value of the pgtIou. This is important because the main (Proxier) application will need to retrieve the pgtId sent to the pgtUrl and the only value it will know is the proxyGrantingTicket IOU (pgtIou).

  3. The main (Proxier) application retrieves the pgtId value from the Application variable stored by the pgtUrl and then calls the RequestProxyTicket
    method passing in the pgtId and the proxy application complete Url.
    Info
    titleThe CAS Proxied Application

    The actual proxy application can be any CAS-enabled application that can call proxyValidate on the supplied proxyTicket and it does not have to reside on the same application server like the pgtUrl and main Proxier applications do._

  4. With the "real" proxyTicket in it's grasp, the main (Proxier) application now calls AddProxyArgument as many times as it needs to add the arguments the proxied application will need. At the bare minimum, the proxyTicket itself MUST be added as an argument.
    Info
    titleThe Proxy Ticket

    The CAS-enabled proxied application MUST have a proxyTicket in order to send it to CAS in the proxyValidate method. The AddProxyArgument method is used in this case by the main application to add the proxyTicket. Then when you call MakeWebRequest , you specify an http method of GET or POST which would determine whether the proxyTicket was sent in the query string or the form post.

  5. Finally, call the MakeWebRequest method passing in the Url to the CAS-enabled application to be proxied along with the http method (e.g. GET or POST).
Tip
titleMaking an Http Request in ASP

The MakeWebRequest method is sort of an ASP version of the System.Net.WebClient class in ASP.Net. You can call this method in any ASP page to make an http GET or POST request and receive an html or xml response depending on the request. This method works very well for CAS proxying because we are just going to Response.Write the returned html of the proxied application to the browser.

The examples I've outlined rely on two classes (source code at the end of this article) that must be included in an include file in order for them to work.
The first is a simple string class since ASP doesn't have the very useful StringBuilder that is always available in ASP.Net,
I created a vbscript mocked up version called clsString (see below). The second class is the CAS_Authenticator and does all the dirty work,
not just for basic CAS authentication with ASP but also supports CAS proxying which is what my examples will demonstrate.

VBScript Code

Panel
borderColor#cccccc
bgColor#ffffff
titleBGColor#eeeeee
titleCASProxier.asp
borderStyledashed

<%@ Language=VBScript %>
<%Option Explicit %>

<!-- #Include File = "../Includes/Public.asp" -->

<HTML>
<HEAD>
<title>Test CAS Proxy Application</title>
</HEAD>
          <body>
<%

Dim objCAS, serviceUrl
Dim pgtId
Dim proxyTicket
Dim proxyAppUrl


'Initialize CAS Authenticator Class
Set objCAS = New CAS_Authenticator


'Set the CAS URL, change accordingly
objCAS.CASURL = "https://auth-test.berkeley.edu/cas"


'Set the current request page Url as the CAS service name
serviceUrl = GetRequestUrl()
If Request.ServerVariables.Item("REQUEST_METHOD") = "POST" Then
    'Get the pgtId, the proxy callback would have stored this as an application variable
    'with the key name as the pgtIou
    pgtId = Application(objCAS.ProxyGrantingTicketIOU)
         
    If IsEmpty(pgtId) Then
        Response.Write "No pgtId found, please make sure your pgtUrl is correct!"
        Response.Write "<BR>"
        Response.End
    End If
         
    'Send off the pgtId as well as the full Url to the CAS-enabled app to be proxied to the
    'RequestProxyTicket method. It will return the "real" proxy ticket to call the proxy with
    proxyAppUrl = "https://orimaging.berkeley.edu/ASP/Proxy/CASProxy.asp"
    proxyTicket = objCAS.RequestProxyTicket(pgtId, proxyAppUrl)
         
    If IsEmpty(proxyTicket) Then
        Response.Write "The proxyTicket is Empty! " & objCAS.LastError
        Response.Write "<BR>"
        Response.End
    End If
         
    'Add arguments to send to proxy, proxyTicket is required as an argument!
    objCAS.AddProxyArgument "proxyTicket", proxyTicket
         
    'Make a request to the proxy and output the response
    Response.Write "Proxy Response:"
    Response.Write "<BR>"
    Response.Write objCAS.MakeWebRequest(proxyAppUrl, "POST")
Else
    If IsEmpty(Application("UID")) Then
        'Since this application will proxy other CAS-enabled apps, we will request
        'a pgtIou/pgtId pair from CAS by supplying a secure Url to our CAS Callback
        objCAS.CASpgtUrl = "https://orimaging.berkeley.edu/ASP/Proxy/ProxyCallback.asp"
        'Validate the URL of this application which will get assigned a ticket
        'unless it fails, in which case exit and explain why it failed
        If Not objCAS.ServiceValidate(serviceUrl) Then
            Response.Write objCAS.LastError
            Response.Write "<BR>"
            Response.Write "Service: " & serviceUrl
            Response.End
        EndIf
            'Set Application variables
            Application.Lock
            Application("UID") = objCAS.netID
            Application.UnLock
    End If
%>
<FORM action="Default.asp" method=post>
<TABLE align=center width="75%">
    <TR>
        <td valign=top align=left>
            <A href="../Logout.asp?url=<%=serviceUrl%>">Log out of CAS</A>
        </td>
    </TR>
    <TR>
        <td valign=top align=left>
            The current time is: <%=now()%>
        </td>
    </TR>
    <TR>
        <td valign=top align=left>
            <span style="FONT-SIZE: 14px; COLOR: teal; FONT-FAMILY: tahoma">
                Welcome <B><%=Application("UID")%></B>, you have successfully authenticated with CAS
            </span>
        </td>
    </TR>
    <TR>
        <td valign=top align=left>
            <INPUT type="submit" value="Test Call Proxy" id="btnCallProxy" name="btnCallProxy">
        </td>
    </TR>
</TABLE>
</FORM>
<%
End If
Set objCAS = Nothing
%>    
          </body>
</HTML>

Panel
borderColor#cccccc
bgColor#ffffff
titleBGColor#eeeeee
titlepgtUrl
borderStyledashed

<%@ Language=VBScript %>
<%Option Explicit %>


<!-- #Include File = "../Includes/Public.asp" -->


<HTML>
<HEAD>
<title>CAS Proxy Callback App</title>
</HEAD>
            <body>           
<%

Dim pgtIou, pgtId


'Get pgtIou in Query String sent from CAS
pgtIou = Request.QueryString.Item("pgtIou")


'Get pgtId in Query String sent from CAS
pgtId = Request.QueryString.Item("pgtId")


'Check to see if they exist
If Len(pgtIou) = 0 OR Len(pgtId) = 0 Then
    Response.Write "No pgtIou/pgtId pair supplied!"
    Response.End
End If


'Store pgtId in application variable accessable from any page within this application
Application.Lock
Application(pgtIou) = pgtId
Application.UnLock
%>
            </BODY>
</HTML>

Panel
borderColor#cccccc
bgColor#ffffff
titleBGColor#eeeeee
titleCAS Proxied Application
borderStyledashed

<%@ Language=VBScript %>
<%Option Explicit %>


<!-- #Include File = "../Includes/Public.asp" -->


<HTML>
<HEAD>
<title>Test CAS Proxy</title>
</HEAD>
            <body>
<%

Dim proxyArgKey
Dim proxyTicket
Dim objCAS


'Initialize CAS Authenticator Class
Set objCAS = New CAS_Authenticator


'Set the CAS URL, change accordingly
objCAS.CASURL = "https://auth-test.berkeley.edu/cas"
'proxy ticket should be in either the query string or the form post
proxyTicket = Request.Item("proxyTicket")


If Not IsEmpty(proxyTicket) Then
    ' validate the proxy application with CAS
    objCAS.ValidateProxy proxyTicket
   
    If NOT IsEmpty(objCAS.LastError) Then
        Response.Write objCAS.LastError
        Response.Write "<BR>"
        Response.End
    End If
   
            %>
            <P>     
            <span style="font-size: 14px;font-family: tahoma;color:teal">
                        Welcome <B><%=objCAS.netID%></B>, you have successfully authenticated with CAS
            </span>
            </P>
            <%
    '
    'Since this is the proxy application, we will check its arguments it was sent
    'Normally we would know if the arguments were sent in the query string or Form post
    'but for the purposes of this example, we will check both
    '
    If Request.QueryString.Count > 0 Then
        'get arguments from querystring object
        For Each proxyArgKey In Request.QueryString
            Response.Write proxyArgKey & "=" & Request.QueryString.Item(proxyArgKey) & "(query string)"
            Response.Write "<BR>"
        Next
    Else
        'get arguments from form object
        For Each proxyArgKey In Request.Form
            Response.Write proxyArgKey & "=" & Request.Form.Item(proxyArgKey) & "(form post)"
            Response.Write "<BR>"
        Next
    End If


    If Not IsEmpty(objCAS.Proxies) Then
        Response.Write "<BR>"
        Response.Write "CAS Proxies:"
        Response.Write "<BR>"
        Response.Write objCAS.Proxies
        Response.Write "<BR>"
    End If
Else
    Response.Write "No proxyTicket found in either the query string or the form post!"
    Response.Write "<BR>"
End If
Set objCAS = Nothing
%>
            </BODY>
</HTML>

Panel
borderColor#cccccc
bgColor#ffffff
titleBGColor#eeeeee
titleclsString
borderStyledashed

'*****************************************************************
' clsString : A String Class helper to aid in string concatenation
'*****************************************************************
Class clsString
    Private m_intLength
    Private m_intCounter
    Private m_arrString()


    Private Sub Class_Initialize()
        'Dim an array and set position counter
         m_intCounter = 1
         m_intLength = 100
         Redim m_arrString(m_intLength)
    End Sub


    Public Sub Reset
        'Erase current array and recreate
         Erase m_arrString
         Call Class_Initialize()
    End Sub


    Public Property Get Value
         'Use Join function to create final string
         Value = Join(m_arrString,"")
    End Property


    Public Property Get Delim(ByVal delimeter)
         'Use Join function to create final string
         Redim Preserve m_arrString(Limit)
         Delim = Join(m_arrString,delimeter)
    End Property


    Public Property Get Element(ByVal n)
         'Use Join function to create final string
         Element = m_arrString(thumbs down)
    End Property


    Public Property Get Limit
         'Use Join function to create final string
         Limit = m_intCounter - 1
    End Property


    Public Sub Add(byval strValue)
        Dim intArrLen
        'Add value to string array
        intArrLen = Ubound(m_arrString)
        If m_intCounter > intArrLen Then _
            Redim Preserve m_arrString(intArrLen + m_intLength)
            m_arrString(m_intCounter) = Cstr(strValue)
            'Increment position counter
            m_intCounter = m_intCounter + 1
    End Sub
End Class


'Start Public Functions
'This function is basically an ASP version of the ASP.Net - Request.Url.GetLeftPart(UriPartial.Path)
Public Function GetRequestUrl()
    Dim Port
    Dim Local_Addr
    Dim Path_Info  
    Dim Server_Name
    Dim Protocol
    Protocol = Request.ServerVariables("SERVER_PORT_SECURE")
    Server_Name = Request.ServerVariables("SERVER_NAME")
    Port = Request.ServerVariables("SERVER_PORT")
    Path_Info = Request.ServerVariables("PATH_INFO")
    If Port = 80 OR Port = 443 Then
        Port = ""
    Else
        Port = ":" & Port
    End If
    Local_Addr = Request.ServerVariables("LOCAL_ADDR")
    If Protocol = "" OR Protocol = "0" Then
        If Local_Addr = "127.0.0.1" Then
            Protocol = "http://"
        Else
            Protocol = "https://"
        End If
    Else
        Protocol = "https://"
    End If
    GetRequestUrl = Protocol & Server_Name & Port & Path_Info
End Function

Panel
borderColor#cccccc
bgColor#ffffff
titleBGColor#eeeeee
titleCAS Authenticator
borderStyledashed

'***************************************************************************
' CAS_Authenticator : A Class wrapper that encapsulates authentication via CAS
'***************************************************************************
Class CAS_Authenticator
    '*********************************************************
    'Private Global variables
    '*********************************************************
    Private m_ErrorText
    Private m_CASURL
    Private m_netID
    Private objXML
    Private srvXmlHttp
    Private proxyArgHash
    Private m_pgtUrl
    Private m_pgtId
    Private m_Proxies
    Private queryCollection   
    Private Sub Class_Initialize()
        Set proxyArgHash = Server.CreateObject("Scripting.Dictionary")
        Set objXML = Server.CreateObject("Microsoft.XMLDOM")       
        'set async for XML Dom
        objXML.async = False      
        Set srvXmlHttp = Server.CreateObject("Msxml2.ServerXMLHTTP")
        Set queryCollection = Server.CreateObject("Scripting.Dictionary")
    End Sub
    Private Sub Class_Terminate()
        Set objXML = Nothing
        Set srvXMLHttp = Nothing
        Set proxyArgHash = Nothing
        Set queryCollection = Nothing
    End Sub
    Public Sub AddProxyArgument(ByVal name, ByVal value)
        If Not proxyArgHash.Exists(name) Then
            proxyArgHash.Add name,value
        EndIf
    End Sub  
    Public Property Get ProxyGrantingTicketIOU
        ProxyGrantingTicketIOU = Application("pgtIou")
    End Property
    Public Property Get Proxies
        Proxies = m_Proxies
    End Property  
    Public Property Get netID
        netID = m_netID
    End Property
    Public Property Get LastError
        LastError = m_ErrorText
    End Property        
    Public Property Let CASpgtUrl(ByVal vNewValue)
        m_pgtUrl = vNewValue
    End Property
    Public Property Let CASURL(ByVal vNewValue)
        m_CASURL = vNewValue
    End Property  
    Public Sub LogOut(ByVal url)
        Session.Abandon()
        Response.Redirect m_CASURL & "/logout?url=" & url
    End Sub        
    Public Function MakeWebRequest(ByVal requestUrl, ByVal httpMethod)
        OnError Resume Next
        Err.Clear                  
        Select Case httpMethod
            Case "GET"
                'request method is a GET
                'Check the proxy arguments
                If proxyArgHash.Count > 0 Then
                    requestUrl = requestUrl & "?" & CollectionToQueryString(proxyArgHash)
                End If
               
                srvXmlHttp.open"GET", requestUrl, false
                srvXmlHttp.send()                  
            Case "POST"
                'request method is a POST
                srvXmlHttp.open"POST", requestUrl, false
                srvXmlHttp.setRequestHeader"Content-Type", "application/x-www-form-urlencoded"
                'Check the proxy arguments
                If proxyArgHash.Count > 0 Then
                    srvXmlHttp.send CollectionToQueryString(proxyArgHash)
                Else
                    srvXmlHttp.send()
                End If                 
            Case Else
                'Return error
                m_ErrorText = "Expecting either 'GET' OR 'POST' http method!"
                MakeWebRequest = Empty
                Exit Function                 
        End Select
        If Err <> 0 Then
            m_ErrorText = Err.Description
            MakeWebRequest = Empty
            Exit Function
        End If      
        If srvXmlHttp.Status <> 200 Then
            m_ErrorText = srvXmlHttp.Status & "- " & srvXmlHttp.StatusText
            MakeWebRequest = Empty
            Exit Function
        End If      
        MakeWebRequest = srvXmlHttp.responseText
    End Function        
    Private Function CollectionToQueryString(ByVal srcCollection)
        Dim srcKey
        Dim strCollItems
        Set strCollItems = New clsString
        For Each srcKey insrcCollection.Keys
            strCollItems.Add srcKey & "=" & srcCollection.Item(srcKey)
        Next                 
        CollectionToQueryString = Mid(strCollItems.Delim("&"), 2)
        Set strCollItems = Nothing
    End Function
    Private Function CASRequest(ByValvalidateurl)
        Dim proxyNodeText
        Dim objCASResponse
        Dim objCASAuthenticationNode
        Dim objCASUser
        Dim objCASProxies
        Dim objCASpgt
        Dim objCASProxy
        Dim objCASproxyTicket
        Dim objProxyList
        Dim htmlResponse
        Dim i
        validateUrl = validateUrl & "?" & CollectionToQueryString(queryCollection)      
        htmlResponse = MakeWebRequest(validateUrl, "GET")                 
        If IsEmpty(htmlResponse) Then
            CASRequest = False
            Exit Function
        End If      
        If objXML.LoadXml(htmlResponse) Then
            'Get reference to cas:serviceResponse XML Node
            Set objCASResponse = objXML.getElementsByTagName("cas:serviceResponse")
            If objCASResponse.length = 0 then
                m_ErrorText = "cas:serviceResponse XML Node is Empty!"
                CASRequest = False
                Exit Function
            End If                                    
            Set objCASAuthenticationNode = objCASResponse.item(0).firstChild
            Select Case objCASAuthenticationNode.nodeName
                Case "cas:authenticationSuccess"
                    'Get CAS user nodeText
                    Set objCASUser = objCASAuthenticationNode.getElementsByTagName("cas:user")
                    If objCASUser.length = 0 then
                        m_ErrorText = "cas:user element NOT present in source CAS XML!"
                        CASRequest = False
                        Exit Function
                    End If                                                        
                    m_netID = objCASUser.item(0).nodeTypedValue
                    'Get CAS proxies nodeText
                    Set objCASProxy = objCASAuthenticationNode.getElementsByTagName("cas:proxies")
                    If objCASProxy.length > 0 then
                        Set objProxyList = New clsString
                        Set objCASProxies = objCASProxy.item(0).childNodes                                                         
                        For i = 0 To objCASProxies.length - 1
                            objProxyList.Add(objCASProxies.item(info) .nodeTypedValue)
                        Next                                                        
                        m_Proxies = objProxyList.Delim(vbcrlf)
                        Set objProxyList = Nothing
                    End If
                    'Get CAS proxyGT nodeText
                    Set objCASpgt = objCASAuthenticationNode.getElementsByTagName("cas:proxyGrantingTicket")
                    If objCASpgt.length > 0 Then
                        Application.Lock
                        Application("pgtIou") = objCASpgt.item(0).nodeTypedValue
                        Application.UnLock
                    End If
                Case "cas:authenticationFailure"
                    m_ErrorText = objCASAuthenticationNode.nodeTypedValue
                    CASRequest = False
                    Exit Function
                Case "cas:proxySuccess"
                    'Sample success returned from CAS
                    '<cas:proxySuccess xmlns:cas="http://www.yale.edu/tp/cas">
                    '   <cas:proxyTicket>
                    '       ST-1625-6YBqesAL6ywgfCwOPQYcK72G6ikQIHc6lab-aws-p1
                    '   </cas:proxyTicket>
                    '</cas:proxySuccess>                                        
                    Set objCASproxyTicket = objCASAuthenticationNode.getElementsByTagName("cas:proxyTicket")
                    If objCASproxyTicket.length > 0 Then
                        m_pgtId = objCASproxyTicket.item(0).nodeTypedValue
                    End If
                Case "cas:proxyFailure"
                    'Sample error returned from CAS
                    '<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
                    '   <cas:proxyFailure code="INVALID_TICKET">
                    '       ticket 'TGT-3053-jqon6B5AIUQ4A6Pb2RlgykerJ99PhUrF0ON-aws-p1' not recognized
                    '   </cas:proxyFailure>
                    '</cas:serviceResponse>
                    m_ErrorText = objCASAuthenticationNode.nodeTypedValue
                    CASRequest = False
                    Exit Function                                        
                Case Else
                    m_ErrorText = "Undefined CAS error!"
                    CASRequest = False
                    Exit Function
            End Select
        Else
            m_ErrorText = "XML Failed to load<XMP>" & htmlResponse & "</XMP>"
            CASRequest = False
            Exit Function
        End If
        'CAS is Authenticated
        CASRequest = True
    End Function        
    Public Function ServiceValidate(ByVal serviceUrl)
        Dim tkt
        Dim URLToValidate                  
        tkt = Request.QueryString.Item("ticket")
        URLToValidate = m_CASURL & "/serviceValidate"
        If IsEmpty(tkt) Then
            'if no ticket in URL then send user to CAS to get one
            Response.Redirect(m_CASURL & "/login?service=" & serviceUrl)
            ServiceValidate = True
            Exit Function
        End If                                       
        ' Second time (back from CAS) there is a ticket= to validate
        queryCollection.RemoveAll
        queryCollection.Add "ticket", tkt
        queryCollection.Add "service", serviceUrl
        If NOT IsEmpty(m_pgtUrl) Then
            queryCollection.Add "pgtUrl", m_pgtUrl
        End If
        If NOT CASRequest(URLToValidate) Then
            ServiceValidate = False
            Exit Function
        End If
        ' If there was a problem, leave the message on the screen. Otherwise, return to original page.
        If IsEmpty(netID) Then
            m_ErrorText = "CAS returned to this application, but then refused to validate your identity."
            ServiceValidate = False
            Exit Function
        End If                 
        ServiceValidate = True
    End Function        
    Public Function RequestProxyTicket(ByVal pgtId, ByVal proxyAppUrl)
        Dim validateurl
        validateurl = m_CASURL & "/proxy"
       
        queryCollection.RemoveAll
        queryCollection.Add "targetService", proxyAppUrl
        queryCollection.Add "pgt", pgtId                  
        If Not CASRequest(validateurl) Then
            RequestProxyTicket = Empty
            Exit Function
        End If
        RequestProxyTicket = m_pgtId
    End Function  
    Public Sub ValidateProxy(ByVal proxyTicket)
        Dim validateurl
        ' Validate proxy ticket
        validateurl = m_CASURL & "/proxyValidate"            
        queryCollection.RemoveAll
        queryCollection.Add "ticket", proxyTicket
        queryCollection.Add "service", GetRequestUrl()
        If Not CASRequest(validateurl) Then
            Exit Sub
        End If
        ' If there was a problem, leave the message on the screen. Otherwise, return to original page.
        If IsEmpty(m_netID) Then
            m_ErrorText = "CAS returned to this application, but then refused to validate your identity."
            Exit Sub
        End If
    End Sub  
End Class