17

I am trying to login to a REST API using HTTP Basic Authentication but it is not working and giving the error

HTTP error 400: Bad Request

Here is my code:

import urllib.parse
import urllib.request
import urllib.response

# create an authorization handler
#auth_handler = urllib.request.HTTPPasswordMgrWithDefaultRealm()
auth_handler = urllib.request.HTTPBasicAuthHandler()


# Add the username and password.
# If we knew the realm, we could use it instead of None.

userName = "username"
passWord  = "pass"
top_level_url = "http URL"
auth_handler.add_password(None, top_level_url, userName,passWord)


# create "opener" (OpenerDirector instance)
opener = urllib.request.build_opener(auth_handler)



# Install the opener.
# Now all calls to urllib.request.urlopen use our opener.
urllib.request.install_opener(opener)

# use the opener to fetch a URL
try:
    result = opener.open(top_level_url)
    #result = urllib.request.urlopen(top_level_url)
    messages = result.read()
    print (messages)  
except IOError as e:
    print (e)
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Vab
  • 179
  • 1
  • 1
  • 3

5 Answers5

37

The following python3 code will work:

import urllib.request
import base64
req = urllib.request.Request(download_url)

credentials = ('%s:%s' % (username, password))
encoded_credentials = base64.b64encode(credentials.encode('ascii'))
req.add_header('Authorization', 'Basic %s' % encoded_credentials.decode("ascii"))

with urllib.request.urlopen(req) as response, open(out_file_path, 'wb') as 
out_file:
    data = response.read()
    out_file.write(data)
Tobias Ernst
  • 4,214
  • 1
  • 32
  • 30
  • 3
    This is what I was forced to do in the end. The Nginx server I'm calling doesn't send a `Www-Authenticate:` header back in its 401 response. My guess is that this means that HTTPBasicAuthHandler stuff in urllib never gets to work. – Dzamo Norton Dec 09 '17 at 07:12
24

Updated for Python 3.x compatibility.

The requests library offers a far easier way of making this sort of request:

import requests

response = requests.get('http://service.example.com',
                        auth=requests.auth.HTTPBasicAuth(
                            'username', 'password'))
print(response.text)

In my own testing this works out fine, while a solution involving urllib.request (like yours, or using the code verbatim from the examples in the documentation) will fail to send the Authentication: header.

larsks
  • 277,717
  • 41
  • 399
  • 399
  • Thanks.To use request I downloaded the zipball and after extracting it .copies the requests directory under my Lib path for python.Is this the expected procedure? I – Vab Apr 18 '15 at 02:59
  • I am geting following error: Exception of type 'HP.PC.API.Model.Exceptions.InvalidAuthenticationDataException' was thrown. 1100 – Vab Apr 18 '15 at 03:19
  • Good to hear. Please accept one of the answers as well. – Anti Veeranna May 14 '15 at 14:12
4

Below code is from https://docs.python.org/3.1/howto/urllib2.html, but not send auth info when request.

# create a password manager
password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()

# Add the username and password.
# If we knew the realm, we could use it instead of None.
top_level_url = "http://example.com/foo/"
password_mgr.add_password(None, top_level_url, username, password)

handler = urllib.request.HTTPBasicAuthHandler(password_mgr)

# create "opener" (OpenerDirector instance)
opener = urllib.request.build_opener(handler)

# use the opener to fetch a URL
opener.open(a_url)

# Install the opener.
# Now all calls to urllib.request.urlopen use our opener.
urllib.request.install_opener(opener)

change the HTTPPasswordMgrWithDefaultRealmtoHTTPPasswordMgrWithPriorAuth and pass is_authenticated=True when call password_mgr.add_password worked for me.

password_mgr = urllib.request.HTTPPasswordMgrWithPriorAuth()
password_mgr.add_password(None, url, username, password, is_authenticated=True)
crazygit
  • 449
  • 4
  • 11
2

I would also use requests library as recommended by larsks, it makes HTTP requests so much easier.

That said, here is a working code sample using urllib

import urllib.parse
import urllib.request
import urllib.response

username = "my_username"
password  = "my_password"
top_level_url = "URL"

# create an authorization handler
p = urllib.request.HTTPPasswordMgrWithDefaultRealm()
p.add_password(None, top_level_url, username, password)

auth_handler = urllib.request.HTTPBasicAuthHandler(p)

opener = urllib.request.build_opener(auth_handler)

urllib.request.install_opener(opener)

try:
    result = opener.open(top_level_url)
    messages = result.read()
    print (messages)
except IOError as e:
    print (e)

Another detail - I tried your own code sample and I got back "http 401 unauthorized", which would be the expected response in case of failed or missing auth.

However you claim that you got http 400 bad request, which leads me to think that you either have the wrong url or there is some other issue as well

four43
  • 1,675
  • 2
  • 20
  • 33
Anti Veeranna
  • 11,485
  • 4
  • 42
  • 63
  • 1
    In my case the above example is very useful. But I see following error --- > urllib.error.URLError: .. .. How to resolve the same? – owgitt Mar 09 '16 at 18:41
2

urllib.request.HTTPBasicAuthHandler() by default uses HTTPPasswordMgr. The HTTPPasswordMgr contains a map that has the password from realm and the top_level_url.

When you perform the request and the server returns 401. The returned HTTP headers contains:

Www-Authenticate: Basic realm="a-value"

The HTTPPasswordMgr searches (user, password) for the returned realm and a new request will be sent with (user, password).

When you write:

auth_handler = urllib.request.HTTPBasicAuthHandler()
# Never use None to realm parameter.
auth_handler.add_password(None, top_level_url, userName,passWord)

You expect that the server sends None realm (but it's not possible). If you want your server to send an empty realm in Www-Autheader, you should use

auth_handler.add_password('', top_level_url, userName,passWord)

You can use HTTPPasswordMgrWithDefaultRealm instead of HTTPPasswordMgr to ignore the returned realm:

auth_handler = urllib.request.HTTPBasicAuthHandler(
    urllib.request.HTTPPasswordMgr()
)
auth_handler.add_password(None, top_level_url, userName,passWord))

If your server sends you a 400 response code, with your sample code, then the authentication was not asked.

tima
  • 1,498
  • 4
  • 20
  • 28