121

Im trying to do a HTTPS GET with basic authentication using python. Im very new to python and the guides seem to use diffrent librarys to do things. (http.client, httplib and urllib). Can anyone show me how its done? How can you tell the standard library to use?

Tom Squires
  • 8,848
  • 12
  • 46
  • 72
  • 2
    Do you want to ensure that the certificate is valid? – Andrew Cox Aug 09 '11 at 16:50
  • 1
    Check out http://stackoverflow.com/questions/635113/python-urllib2-basic-http-authentication-and-tr-im . It seems to cover exactly what you're looking for. – Geo Aug 09 '11 at 17:10

8 Answers8

177

In Python 3 the following will work. I am using the lower level http.client from the standard library. Also check out section 2 of rfc2617 for details of basic authorization. This code won't check the certificate is valid, but will set up a https connection. See the http.client docs on how to do that.

from http.client import HTTPSConnection
from base64 import b64encode


# Authorization token: we need to base 64 encode it 
# and then decode it to acsii as python 3 stores it as a byte string
def basic_auth(username, password):
    token = b64encode(f"{username}:{password}".encode('utf-8')).decode("ascii")
    return f'Basic {token}'

username = "user_name"
password = "password"

#This sets up the https connection
c = HTTPSConnection("www.google.com")
#then connect
headers = { 'Authorization' : basic_auth(username, password) }
c.request('GET', '/', headers=headers)
#get the response back
res = c.getresponse()
# at this point you could check the status etc
# this gets the page text
data = res.read()  
ntg
  • 12,950
  • 7
  • 74
  • 95
Andrew Cox
  • 10,672
  • 3
  • 33
  • 38
  • 5
    The `request` method documentation[1] mentions that "Strings are encoded as "ISO-8859-1", the default charset for HTTP". So i suggest to decode with "ISO-8859-1" instead of "ASCII". [1] https://docs.python.org/3/library/http.client.html#http.client.HTTPConnection.request – jgomo3 Nov 13 '14 at 19:29
  • 35
    To use variables instead of `b"username:password"`, use: `bytes(username + ':' + password, "utf-8")`. – kenorb May 03 '15 at 14:11
  • 4
    @jgomo3: The `.decode("ascii")` is only for the `bytes` -> `str` conversion. The result of `b64encode` is ASCII-only anyway. – Torsten Bronger Oct 29 '16 at 10:45
  • 1
    My savior. After 4 hours of struggling and a load of miss-direction. –  Jan 21 '20 at 16:16
  • How do i use default credentials?, this wont work if i run the code in another system right? – anandhu Aug 12 '20 at 02:10
  • @kenorb: the bytes function just take only one argument. How can we solve this problem? – Thịnh Kều Aug 12 '20 at 10:45
  • just not aligned with the PEP (see python.org) conventions (variable names) – Eugene Lycenok Aug 27 '20 at 04:47
  • Awesome, after several hours of trial and error, I got the perfect solution to work with Wordpress REST APIs using Python as Programming Language. Thanks a ton. – Durga Viswanath Gadiraju Jan 14 '21 at 15:38
  • 2 nights! that is how long I spent trying to find a solution. Thank you so much... though I'm very curios to know why socket, request etc failed where as http.client worked. – yanger rai Jul 24 '22 at 22:28
  • basically username and password needs to be encoded to a token... Set a function to do this.... – ntg Nov 16 '22 at 11:18
136

Use the power of Python and lean on one of the best libraries around: requests

import requests

r = requests.get('https://my.website.com/rest/path', auth=('myusername', 'mybasicpass'))
print(r.text)

Variable r (requests response) has a lot more parameters that you can use. Best thing is to pop into the interactive interpreter and play around with it, and/or read requests docs.

ubuntu@hostname:/home/ubuntu$ python3
Python 3.4.3 (default, Oct 14 2015, 20:28:29)
[GCC 4.8.4] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> r = requests.get('https://my.website.com/rest/path', auth=('myusername', 'mybasicpass'))
>>> dir(r)
['__attrs__', '__bool__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__nonzero__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_content', '_content_consumed', 'apparent_encoding', 'close', 'connection', 'content', 'cookies', 'elapsed', 'encoding', 'headers', 'history', 'iter_content', 'iter_lines', 'json', 'links', 'ok', 'raise_for_status', 'raw', 'reason', 'request', 'status_code', 'text', 'url']
>>> r.content
b'{"battery_status":0,"margin_status":0,"timestamp_status":null,"req_status":0}'
>>> r.text
'{"battery_status":0,"margin_status":0,"timestamp_status":null,"req_status":0}'
>>> r.status_code
200
>>> r.headers
CaseInsensitiveDict({'x-powered-by': 'Express', 'content-length': '77', 'date': 'Fri, 20 May 2016 02:06:18 GMT', 'server': 'nginx/1.6.3', 'connection': 'keep-alive', 'content-type': 'application/json; charset=utf-8'})
IvanD
  • 7,971
  • 4
  • 35
  • 33
23

Update: OP uses Python 3. So adding an example using httplib2

import httplib2

h = httplib2.Http(".cache")

h.add_credentials('name', 'password') # Basic authentication

resp, content = h.request("https://host/path/to/resource", "POST", body="foobar")

The below works for python 2.6:

I use pycurl a lot in production for a process which does upwards of 10 million requests per day.

You'll need to import the following first.

import pycurl
import cStringIO
import base64

Part of the basic authentication header consists of the username and password encoded as Base64.

headers = { 'Authorization' : 'Basic %s' % base64.b64encode("username:password") }

In the HTTP header you will see this line Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=. The encoded string changes depending on your username and password.

We now need a place to write our HTTP response to and a curl connection handle.

response = cStringIO.StringIO()
conn = pycurl.Curl()

We can set various curl options. For a complete list of options, see this. The linked documentation is for the libcurl API, but the options does not change for other language bindings.

conn.setopt(pycurl.VERBOSE, 1)
conn.setopt(pycurlHTTPHEADER, ["%s: %s" % t for t in headers.items()])

conn.setopt(pycurl.URL, "https://host/path/to/resource")
conn.setopt(pycurl.POST, 1)

If you do not need to verify certificate. Warning: This is insecure. Similar to running curl -k or curl --insecure.

conn.setopt(pycurl.SSL_VERIFYPEER, False)
conn.setopt(pycurl.SSL_VERIFYHOST, False)

Call cStringIO.write for storing the HTTP response.

conn.setopt(pycurl.WRITEFUNCTION, response.write)

When you're making a POST request.

post_body = "foobar"
conn.setopt(pycurl.POSTFIELDS, post_body)

Make the actual request now.

conn.perform()

Do something based on the HTTP response code.

http_code = conn.getinfo(pycurl.HTTP_CODE)
if http_code is 200:
   print response.getvalue()
Ocaj Nires
  • 3,295
  • 1
  • 18
  • 10
19

A correct way to do basic auth in Python3 urllib.request with certificate validation follows.

Note that certifi is not mandatory. You can use your OS bundle (likely *nix only) or distribute Mozilla's CA Bundle yourself. Or if the hosts you communicate with are just a few, concatenate CA file yourself from the hosts' CAs, which can reduce the risk of MitM attack caused by another corrupt CA.

#!/usr/bin/env python3


import urllib.request
import ssl

import certifi


context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
context.verify_mode = ssl.CERT_REQUIRED
context.load_verify_locations(certifi.where())
httpsHandler = urllib.request.HTTPSHandler(context = context)

manager = urllib.request.HTTPPasswordMgrWithDefaultRealm()
manager.add_password(None, 'https://domain.com/', 'username', 'password')
authHandler = urllib.request.HTTPBasicAuthHandler(manager)

opener = urllib.request.build_opener(httpsHandler, authHandler)

# Used globally for all urllib.request requests.
# If it doesn't fit your design, use opener directly.
urllib.request.install_opener(opener)

response = urllib.request.urlopen('https://domain.com/some/path')
print(response.read())
saaj
  • 23,253
  • 3
  • 104
  • 105
  • This is great. Certificate verification is important when sending plain text credentials (HTTP Basic Auth). You need to make sure your TLS layer (HTTPS) is secure then because you’re relying on that layer to be secure. – four43 Jan 27 '18 at 17:22
  • Looks correct, but did not work in my case, it is throwing error like ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1056) – neelmeg Jun 25 '19 at 05:17
  • I figured it by passing a valid pem certificate to the verify param and cookies param. – neelmeg Jun 30 '19 at 05:54
9

Based on the @AndrewCox 's answer with some minor improvements:

from http.client import HTTPSConnection
from base64 import b64encode


client = HTTPSConnection("www.google.com")
user = "user_name"
password = "password"
headers = {
    "Authorization": "Basic {}".format(
        b64encode(bytes(f"{user}:{password}", "utf-8")).decode("ascii")
    )
}
client.request('GET', '/', headers=headers)
res = client.getresponse()
data = res.read()

Note, you should set encoding if you use bytes function instead of b"".

I159
  • 29,741
  • 31
  • 97
  • 132
7
requests.get(url, auth=requests.auth.HTTPBasicAuth(username=token, password=''))

If with token, password should be ''.

It works for me.

iminiki
  • 2,549
  • 12
  • 35
  • 45
yidong li
  • 81
  • 1
  • 3
5

using only standard modules and no manual header encoding

...which seems to be the intended and most portable way

the concept of python urllib is to group the numerous attributes of the request into various managers/directors/contexts... which then process their parts:

import urllib.request, ssl

# to avoid verifying ssl certificates
httpsHa = urllib.request.HTTPSHandler(context= ssl._create_unverified_context())

# setting up realm+urls+user-password auth
# (top_level_url may be sequence, also the complete url, realm None is default)
top_level_url = 'https://ip:port_or_domain'
# of the std managers, this can send user+passwd in one go,
# not after HTTP req->401 sequence
password_mgr = urllib.request.HTTPPasswordMgrWithPriorAuth()
password_mgr.add_password(None, top_level_url, "user", "password", is_authenticated=True)

handler = urllib.request.HTTPBasicAuthHandler(password_mgr)
# create OpenerDirector
opener = urllib.request.build_opener(handler, httpsHa)

url = top_level_url + '/some_url?some_query...'
response = opener.open(url)

print(response.read())
alexey
  • 401
  • 4
  • 7
-3

GET & POST request is usually used to submit forms. Here is a brief example of its usage

Views.py

def index(request)
    col1 = float(request.GET.get('col1'))

index.html

<div class="form-group col-md-2">
        <label for="Col 1">Price</label>
        <input type="number" class="form-control" id="col1" name="col1">
    </div>
CodePak
  • 93
  • 1
  • 12