Client Auth Using HTML Forms

Problem this snippet solves:

This example shows how you can set-up the BigIP AUTH services to use HTML Form Based authentication of clients.

This example needs to be updated if used on a version greater than v9

In this example a pre-defined class is used to define URI's which require authentication prior to access. If an un-authenticated user attempts to access one of the pre-defined URI's they are re-directed to a HTML form which is served from the BigIP (though there is no reason you couldn't serve it from the server). The re-direct also includes a URI query string which is a base64 encoded version of the original URI the user was trying to access, this will be used later.

When the BigIP detects the form POST it extracts the username and password variables and then authenticates the user; in this case against an ldap server. On successful authentication, a session entry is created in the persistence table using the authID as the key and the AUTH::status as the value. The user is then re-directed to the original URI they requested and the re-direct response also includes the clients authID as an encrypted cookie.

When the client browser connects back the original URI and passes the encrypted authID (from the cookie) the authID is used as a lookup to find the users AUTH::status and if the AUTH::status = 0 (which means the user is authenticated) then the user request is allowed to pass through to the server.

NOTE: In version 10.x of BIG-IP you can no longer reference a class directly as shown below. Instead you'll need to use the class command to retrieve the class data.

set loginform [b64decode [lindex $::loginFormclass 0]]

  • Will no longer be valid as referencing a class directly like a TCL list will simply result in the class name being returned, not the contents of the class.

Code :

# Data Group Lists

class loginForm_class {
   type string
   filename "/var/class/loginForm.class"
}
class private_access {
   "/SecureDir1"
   "/membersArea"
}


# NOTE: The loginForm.class is just a base64 encoded HTML page which is then wrapped in "quotes". A sample is included at the bottom of this page.

when RULE_INIT {
  set ::aeskey [AES::key 128]
} 

when CLIENT_ACCEPTED {
  set forceauth 1
  set auth_status 2
  set ckname BIGIP_AUTH
  set ckpass myPassword
  set asid [AUTH::start pam default_ldap]
}

when HTTP_REQUEST {
  if { [matchclass [HTTP::path] starts_with $::private_access] } {
    # Private URI, Auth Required
    if { [HTTP::cookie exists $ckname] } {
      set cookie_payload [HTTP::cookie value $ckname]
      set decryptedCookie [AES::decrypt $::aeskey [b64decode $cookie_payload ]]
      if { not ( $decryptedCookie equals "" ) } {
        log local0. "Decrypted Cookie=$decryptedCookie"
        # retrieve the auth status from the session table
        set auth_status [session lookup uie $decryptedCookie]
      }
      # If the auth status is 0 then the user is authenticated
      if { $auth_status eq 0 } {
        #Cookie Decrypted & Session Auth valid 
        set forceauth 0
      }
    }
    if {$forceauth eq 1} {
      set orig_uri [b64encode [HTTP::uri]]
      HTTP::redirect "/Login_form?req=$orig_uri"
    }
  } else {
    # If the user is re-directed to the login form then serve the login form from the BigIP
    if { [HTTP::path] starts_with "/Login_form" && [HTTP::method] equals "GET" } {
      # Retrieve the login form from a base64 encoded external class file
      set login_form [b64decode [lindex $::loginForm_class 0]]
      HTTP::respond 200 content $login_form "Content-Type" "text/html"
    } elseif { [HTTP::path] starts_with "/Login_form" && [HTTP::method] equals "POST" } {
      # Process the login form and auth the user
      # Decode the original URI from the req parameter so we can re-direct to the original
      # URI on sucessful auth
      set orig_uri [ b64decode [URI::query [HTTP::request] "req" ] ] 
      HTTP::collect [HTTP::header Content-Length]
    }
  }
}
when HTTP_REQUEST_DATA {
  set namevals [split [HTTP::payload] "&"]
  # Break out the POST data for username and password values
  for {set i 0} {$i < [llength $namevals]} {incr i} {
    set params [split [lindex $namevals $i] "="]
    if { [lindex $params 0] equals "username" } {
      set auth_username [lindex $params 1]
    }
    if { [lindex $params 0] equals "password" } {
      set auth_password [lindex $params 1]
    }
  }
  AUTH::username_credential $asid $auth_username
  AUTH::password_credential $asid $auth_password
  AUTH::authenticate $asid
  HTTP::collect
}

when AUTH_SUCCESS {
  if {$asid eq [AUTH::last_event_session_id]} {
    
    # Now the user has authenticated lets give them an encrypted cookie with their authID
    # We'll also add the AUTH::status to a session entry with the authID as the key
    # We can then re-direct the user to the page they originally asked for
    set authStatus [AUTH::status $asid] 
    session add uie $asid $authStatus 1800
    set encrypted_asid [b64encode [AES::encrypt $::aeskey $asid]]
    set authcookie [format "%s=%s; path=/; " $ckname $encrypted_asid ]
    HTTP::respond 302 Location $orig_uri "Set-Cookie" $authcookie
  }
}

when AUTH_FAILURE {
  if {$asid eq [AUTH::last_event_session_id]} {
           HTTP::respond 200 content "Authentication Failed" 
  }
}

when AUTH_WANTCREDENTIAL {
  if {$asid eq [AUTH::last_event_session_id]} {
           HTTP::respond 200 content "Authentication Credentials not provided"
  }
}

when AUTH_ERROR {
  if {$asid eq [AUTH::last_event_session_id]} {
  HTTP::respond 200 content "Authentication Error"
  }
}

# Configuration: To successfully configure this iRule, it needs to be applied to an "Authentication Profile" and the "Authentication Profile" applied to the Virtual Server. If you attempt to apply it directly to a Virtual Server you will get a configuration error.

# Sample loginForm.html




<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
Login



UERNAME:
PASSWORD:
 
# Sample loginForm.class - Base64 encoded version of loginForm.html. Take note of the "quotes" before and after the base64 code. "PCFET0NUWVBFIEhUTUwgUFVCTElDICItLy9XM0MvL0RURCBIVE1MIDQuMDEgVHJhbnNpdGlvbmFs Ly9FTiINCiJodHRwOi8vd3d3LnczLm9yZy9UUi9odG1sNC9sb29zZS5kdGQiPg0KPGh0bWw+DQo8 aGVhZD4NCjxtZXRhIGh0dHAtZXF1aXY9IkNvbnRlbnQtVHlwZSIgY29udGVudD0idGV4dC9odG1s OyBjaGFyc2V0PWlzby04ODU5LTEiPg0KPHRpdGxlPkxvZ2luPC90aXRsZT4NCjxzdHlsZSB0eXBl PSJ0ZXh0L2NzcyI+DQo8IS0tDQpib2R5LHRkLHRoIHsNCglmb250LWZhbWlseTogR2VuZXZhLCBB cmlhbCwgSGVsdmV0aWNhLCBzYW5zLXNlcmlmOw0KfQ0KLnN0eWxlNCB7Zm9udC1zaXplOiAxMnB4 fQ0KLS0+DQo8L3N0eWxlPg0KPC9oZWFkPg0KPGJvZHk+DQo8Zm9ybSBhY3Rpb249IiIgbWV0aG9k PSJwb3N0IiBuYW1lPSJsb2dpbkZvcm0iIGlkPSJsb2dpbkZvcm0iPg0KICAJPHRhYmxlIHdpZHRo PSIyNjIiIGJvcmRlcj0iMCI+DQogICAgICA8dHI+DQogICAgICAgIDx0ZCB3aWR0aD0iMTAyIj48 ZGl2IGFsaWduPSJyaWdodCIgY2xhc3M9InN0eWxlNCI+VUVSTkFNRTo8L2Rpdj48L3RkPg0KICAg ICAgICA8dGQgd2lkdGg9IjE1MCI+PGlucHV0IG5hbWU9InVzZXJuYW1lIiB0eXBlPSJ0ZXh0IiBp ZD0idXNlcm5hbWUiPjwvdGQ+DQogICAgICA8L3RyPg0KICAgICAgPHRyPg0KICAgICAgICA8dGQ+ PGRpdiBhbGlnbj0icmlnaHQiIGNsYXNzPSJzdHlsZTQiPlBBU1NXT1JEOjwvZGl2PjwvdGQ+DQog ICAgICAgIDx0ZD48aW5wdXQgbmFtZT0icGFzc3dvcmQiIHR5cGU9InBhc3N3b3JkIiBpZD0icGFz c3dvZCI+PC90ZD4NCiAgICAgIDwvdHI+DQogICAgICA8dHI+DQogICAgICAgIDx0ZD4mbmJzcDs8 L3RkPg0KICAgICAgICA8dGQ+PGlucHV0IHR5cGU9InN1Ym1pdCIgbmFtZT0iU3VibWl0IiB2YWx1 ZT0iU1VCTUlUIiBzdHlsZT0iLnN0eWxlNCI+PC90ZD4NCiAgICAgIDwvdHI+DQogICAgPC90YWJs ZT4NCiAgCTxwPiZuYnNwOzwvcD4NCjwvZm9ybT4NCjwvYm9keT4NCjwvaHRtbD4NCg==" # Here's a copy of the same b64 encoding without CR/LF: SampleLoginFormClassWithoutCRLF
Updated Jun 06, 2023
Version 2.0

Was this article helpful?

No CommentsBe the first to comment