Form Based Authentication with external SOAP web services

Problem this snippet solves:

1- You need to authenticate users against an external authentication system relying on SOAP calls.

2- The session identifier must be provided by an external third party system.

How to use this snippet:

Installation

Files

You need to upload an html login page using ifiles.

You need to upload the SOAP body of the external web service using ifiles.

irule

You need to install the irule on your Virtual Server you need to protect.

Variables

set static::holdtime 3600                               # session timeout
set static::login_url "/login"                          # login url 
set static::sideband_vs "VS_EXTERNAL_AUTH_PROVIDER"     # Virtual Server that publish the web service

Features

Version 1.0

  • Form based login (provided by a custom ifile)
  • Authentication against an external SOAP web service
  • Manage Session timeout

Backlog

  • Improve logging
  • Allow 2-factor authentication (Challenge)
  • Encrypt Session cookie
  • Provide internal mecanism to generate a session cookie
  • accept Basic Authentication

External links

Github : https://github.com/e-XpertSolutions/f5

Code :

when RULE_INIT {
set static::holdtime 3600
set static::login_url "/login"
set static::sideband_vs "VS_EXTERNAL_AUTH_PROVIDER"
}

when HTTP_REQUEST {
if { [HTTP::cookie exists SessionCook] and [table lookup -subtable "active_sessions" [HTTP::cookie SessionCook]] != "" } {
return
} else {
if { [HTTP::path] eq $static::login_url } {
if { [HTTP::method] eq "POST" } {
if {[HTTP::header "Content-Length"] ne "" && [HTTP::header "Content-Length"] <= 1048576}{
set content_length [HTTP::header "Content-Length"]
} else {
set content_length 1048576
}
if { $content_length > 0} {
HTTP::collect $content_length
}
} else {
HTTP::respond 200 content [ifile get login.html] "Cache-Control" "no-cache, must-revalidate" "Content-Type" "text/html"
}
} else {
HTTP::respond 302 noserver "Location" $static::login_url "Cache-Control" "no-cache, must-revalidate" Set-Cookie "SessionCook=$result;domain=[HTTP::host];path=/"
}
}
}

when HTTP_REQUEST_DATA {
set payload [HTTP::payload]
set username ""
set password ""
regexp {Login1\%3AtxtUserName\=(.*)\&Login1\%3AtxtPassword\=(.*)\&Login1\%3AbtnSubmit\=(.*)} $payload -> username password garbage
  
if {[catch {connect -timeout 1000 -idle 30 -status conn_status $static::sideband_vs} conn_id] == 0 && $conn_id ne ""}{
log local0. "Connect returns: $conn_id and conn status: $conn_status"
} else {
log local0. "Connection could not be established to sideband_virtual_server"
}

set content [subst -nocommands -nobackslashes [ifile get soap_body]]
set length [string length $content]
set data "POST /apppath/webservicename.asmx HTTP/1.1\r\nHost: www.hostname.com\r\nContent-Type: text/xml; charset=utf-8\r\nContent-Length: $length\r\nSOAPAction: http://schemas.microsoft.com/sqlserver/2004/SOAP\r\n\r\n$content"

set send_bytes [send -timeout 1000 -status send_status $conn_id $data]
set recv_data [recv -timeout 1000 $conn_id]

# parse response to retrieve the authentication result, it gives 0 if authentication failed or a session_id if it succeed 
regexp {(.*)(.*)} $recv_data -> result garbage

unset content
unset length
unset data
unset recv_data
close $conn_id

# add a custom alert notification to the login page


if { $result == 0 } {
set alert "
Invalid credentials.
" HTTP::respond 200 content [subst -nocommands -nobackslashes [ifile get login.html]] "Cache-Control" "no-cache, must-revalidate" "Content-Type" "text/html" Set-Cookie "SessionCook=deleted;expires=Thu, 01-Jan-1970 00:00:10 GMT;domain=[HTTP::host];path=/" } else { HTTP::respond 302 noserver "Location" "/" "Cache-Control" "no-cache, must-revalidate" Set-Cookie "SessionCook=$result;domain=[HTTP::host];path=/" # save the cookie value in a cache for fast checking table add -subtable "active_sessions" $result $username indef $static::holdtime } }

Tested this on version:

11.5
Updated Jun 06, 2023
Version 2.0