Forum Discussion

Erich_Rockman_1's avatar
Dec 10, 2015

Check Authorization / WWW-Authenticate headers

Hi. I am trying to make sure that the user what is trying to/has authenticated to a site w/ Basic Auth matches a user in a list. However, I cannot force the server to prompt if the user/pass is not accepted by the web server. It just responds with the Access is Denied message. I only want the check to take place if the web server allows the auth. Any ideas? Thanks.

 

if { ([HTTP::header value Authorization] ne "") } { Format should be: Basic dXNlcm5hbWU6cGFzc3dvcmQ= where the token is a base64 encoding of user:pass

 

set basicuserid [string tolower [getfield [b64decode [lindex [HTTP::header Authorization] 1]] ":" 1]] if the basic auth user is not in our list ($UserList) then drop request

 

if { [lsearch $UserList $basicuserid] equals -1 } { ACCESS::session remove HTTP::respond 200 content "Access is Denied!" reject } }

 

16 Replies

  • Hi Erich,

     

    I'm not sure if I got every detail of your explanation, but it looks like you are aiming for a simple "username" access-list/filter infront of the real server. Right?

     

    Then you may try this code...

     

    set userlist "foo bar"
    catch HTTP::username username
    if { [lsearch $userlist $username] equals -1 } then { 
        ACCESS::session remove 
        HTTP::respond 401 content "Authentication Required" \
                         "Content-Type" "text/html" \
                         "WWW-Authenticate" "Basic realm=\"[HTTP::host]\""
        return
    }

    BTW: If your application doesn't even send an error code 401 on allowed username but wrong passwords, then you have to provide additional information about the HTTP::response the server is sending.

     

    Cheers, Kai

     

  • I am not looking to filter before the server, but after. If I enter an incorrect user/pass, I don't even get prompted to re-enter it. It just goes to the Access Denied.

     

    • Kai_Wilke's avatar
      Kai_Wilke
      Icon for MVP rankMVP
      Hi Erich, okay... didnt got the response part since your provided code was related to a typical HTTP request. To overwite/replace the behavior of your server we would need additional infomations. So please elaborate some additional details (aka. If possible the entire response header/content) of the received error. Cheers, Kai
  • It's not an error. I am returning the Access is Denied to the client in an HTTP::respond. The Authorization header is sent with the request and the WWW-Authenticate header is sent in the response. I am not looking to overwrite/replace the server response, I am looking to check that the user that is successfully authenticated by the server matches a username in a list that I provide. It seems like I cannot do both.

     

    • Kai_Wilke's avatar
      Kai_Wilke
      Icon for MVP rankMVP
      Hi Erich, I dont understand why you want the validity check happen "after" the user has already logged in to your server? In my opinion, its far more effective to check already on HTTP_REQUEST if the username is whitelisted and depending on your desired action to "ask for credentials" or simply send a "access denied message" if an unknown or no username was submitted. In addition a HTTP_RESPONSE filter could be implemented to check if the authentication was denied for the already whitelisted username. The check could then supress/change/manipulate the response if needed to either become a "ask again for credentials" (aka. 401) or "access denied message" (aka. 403) response. Please describe your needs and the intention behind as best as possible. It will allow us to help you without assuming things. Thanks! Cheers, Kai
  • Erich,

    Do you want to block if the user provide a wrong password and is prompted again by the server?

    when HTTP_REQUEST {
        if {[catch {set username [HTTP::username] }]} { set username ""}
        if { $username ne ""} { 
            set user_provided 1
        } else {
            set user_provided 0
        }
    
    }
    
    when HTTP_RESPONSE {
      if { ([HTTP::status] eq "401") && ($user_provided) } { HTTP::respond 403 content "Access is Denied!" }
    }
    
  • OK,

     

    Tell me if I am wrong:

     

    • you have a list of authorized users: userlist
    • if a user try to authenticate with a user in the userlist, he will be able to authenticate to backend server
    • if the user is not in the userlist, he will receive a block page from BigIP

    What is the expected behavior if the user does not provide any authentication header (default mode of the first request from a browser)

     

  • I only check if the user matches if the Authorization header is not "". However, even when the user enters an incorrect user and/or password, the Authorization header is set. But if this user and/or pass is incorrect (server auth), I want it to prompt again. Right now, it does not because the Authorization header is set.

     

    • Stanislas_Piro2's avatar
      Stanislas_Piro2
      Icon for Cumulonimbus rankCumulonimbus
      please explain your need with conditions and associated action as I wrote in my previous answer. With Kai, we are trying to help you but we don't understand your need.
    • Kai_Wilke's avatar
      Kai_Wilke
      Icon for MVP rankMVP
      Hi Erich, as Stanislas already told the outlined scenario is for us both still somewhat unclear. So i guess it would be realy helpful to answer the following basic questions at first o understand your specific needs. 1.) Has the server/application any special behavior to overcome? Or is it a RFC compliant Basic authentication via err401 and WWW-Authenticate? 2.) Do you have special security concerns that must be covered? (e.g. allow just a few users to login, change the default behavior of the server in a certain way, etc.) Remark: Please don't think in iRules or TCL Code. Only explain the situation before you've started and the solution you need in the end. Thanks! Cheers, Kai
  • I have been trying to explain it, but maybe because I have been deep into this, I am not doing a great job of it. I'll try again.

     

    It is a regular web server, 401, Authorization, WWW-Authenticate headers intact.

     

    I am looking to check the user credentials (entered into the browser via 401 challenge) and check that value against a list. That works fine right now. However, I cannot get it to respect the server's response (WWW-Authenticate). I need to be able to continue to prompt the user for creds when they are not correct (as far as Basic Auth is concerned).

     

    My problem is that the WWW-Authenticate comes back on the response side and I am checking the Authorization header on the request side.

     

    • Kai_Wilke's avatar
      Kai_Wilke
      Icon for MVP rankMVP
      If the server is compliant, then it would send a 401 on each request which is not authenticated. This will repeat endless, since Basic authentication is per-request based and does not have a clue of previous requests. But the browser may supress further auth prompts from poping up after a couple of trys and display instead the content of the 401 response. If a user is already sucessfull authenticated but the page is not accesible (e.g. the specific user dont have permissions), then the server could either promt for additional credentials or display a 403 access denied message. But this is a vendor/product specific decission/setting. Which of the above scenarios is not working as desired? Cheers, Kai
    • Erich_Rockman_1's avatar
      Erich_Rockman_1
      Icon for Cirrus rankCirrus
      The server is sending a 401 response. However, how can I perform a check on a response code in a HTTP_REQUEST event. That would be the idea. If status eq 401, then don't perform by logic of checking the username against the list. However, I cannot see the 401 on the request side after entering user/pass into browser.
    • Kai_Wilke's avatar
      Kai_Wilke
      Icon for MVP rankMVP
      You simply can't check the response code in the HTTP request stage, since you would need to look into the future to get those information. You could be creative and try to relate the last response with the next request. But this is strongly not recomended, since it would not comply with the per-request nature of HTTP in combination with Basic auth. All you should do is relate a HTTP request to the corresponding HTTP response. So you may store information in the request and based on that perform certain actions on the response. Cheers, Kai
  • Does this irule do what you want?

    when HTTP_REQUEST {
        if {[catch {set username [HTTP::username] }]} { set username ""}
        if { ($username ne "") && ([lsearch $userlist $username] equals -1 )} { HTTP::respond 403 content "Access is Denied!" }   
    }
    
    when HTTP_RESPONSE {
      if { ([HTTP::status] eq "401") && !([HTTP::header WWW-Authenticate] contains "Basic") } { HTTP::respond 403 content "Access is Denied!" }
    }
    
  • Erich,

     

    in your irule, replace the code:

     

    set basicuserid [string tolower [getfield [b64decode [lindex [HTTP::header Authorization] 1]] ":" 1]]

    with:

     

    if {[catch {set basicuserid [HTTP::username] }]} { set basicuserid ""}

    It does the same thing but with the F5 header.