Al interactuar con APIs o servidores web mediante la biblioteca requests en Python, es común enfrentarse al código de estado HTTP 401 (No Autorizado). Este error indica que el serviddor requiere credenciales válidas para procesar la solicitud. A continuación, se detallan los escenarios más frecuentes y sus respectivas soluciones técnicas.
Escenario 1: Credenciales en la URL
En algunos navegadores, al acceder a ciertos recursos protegidos, se despliega un cuadro de diálogo nativo solicitando usuario y contraseña. A nivel de protocolo, esto suele corresponder a una autenticación básica. Una forma rápida, aunque no siempre recomendada por temas de seguridad y trazabilidad, es incrustar las credenciales directamente en la URL utilizando el formato http://usuario:contraseña@dominio.
Escenario 2: Solicitud estándar sin autenticación
Si realizamos una petición convencional a un endpoint protegido sin proporcionar mecanismos de autenticación, el servidor responderá con un error 401 y un mensaje de denegación de acceso.
import requests
def verificar_acceso_sin_credenciales():
endpoint = "http://servidor-interno.corp/api/data"
respuesta = requests.get(endpoint)
print(f"Código de estado: {respuesta.status_code}")
print(f"Contenido: {respuesta.text[:100]}...")
if __name__ == "__main__":
verificar_acceso_sin_credenciales()
Solución 1: Autenticación Básica (Basic Auth)
El método más habitual es la autenticación básica, donde el usuario y la contraseña se codifican en Base64 y se envían en el encabezado Authorization. La biblioteca requests facilita esto mediante la clase HTTPBasicAuth o pasando una tupla directamente.
import requests
from requests.auth import HTTPBasicAuth
def ejecutar_peticion_basica():
target_url = "http://servidor-interno.corp/api/data"
username = "admin_user"
secret_key = "SuperSecret123"
# Opción A: Usando la clase explícita
auth_handler = HTTPBasicAuth(username, secret_key)
resp = requests.get(target_url, auth=auth_handler)
# Opción B: Pasando una tupla (más conciso)
# resp = requests.get(target_url, auth=(username, secret_key))
print(f"Resultado Basic Auth: {resp.status_code}")
if __name__ == "__main__":
ejecutar_peticion_basica()
Solución 2: Autenticación por Resumen (Digest Auth)
Si el servidor exige un nivel de seguridad ligeramente superior al básico, puede requerir autenticación por resumen (Digest). En este caso, las credenciales no viajan en texto plano, sino que se aplica un algoritmo de hash.
import requests
from requests.auth import HTTPDigestAuth
def peticion_con_digest():
api_endpoint = "http://servidor-interno.corp/secure-area"
usr = "admin_user"
pwd = "SuperSecret123"
digest_auth = HTTPDigestAuth(usr, pwd)
http_resp = requests.get(api_endpoint, auth=digest_auth)
print(f"Resultado Digest Auth: {http_resp.status_code}")
if __name__ == "__main__":
peticion_con_digest()
Solución 3: Autenticación NTLM
Si tras implementar Basic o Digest Auth el servidor sigue devolviendo 401, es fundamental inspeccionar los enacbezados de la respuesta para identificar el esquema de autenticación requerido.
import requests
def inspeccionar_encabezados():
url_objetivo = "http://servidor-interno.corp/secure-area"
resp = requests.get(url_objetivo)
print(f"Esquema requerido: {resp.headers.get('WWW-Authenticate')}")
if __name__ == "__main__":
inspeccionar_encabezados()
Si la salida muestra algo como Negotiate, NTLM, significa que el servidor (comúnmente IIS de Microsoft) está utilizando el protocolo NTLM de Windows. Para manejar esto, es necesario instalar la extensión requests_ntlm (pip install requests_ntlm) y utilizar su manejador específico.
import requests
from requests_ntlm import HttpNtlmAuth
def autenticacion_ntlm():
recurso = "http://servidor-interno.corp/secure-area"
# En NTLM, a veces se requiere el dominio: 'DOMINIO\\usuario'
cuenta = "CORP\\admin_user"
clave = "SuperSecret123"
ntlm_handler = HttpNtlmAuth(cuenta, clave)
resultado = requests.get(recurso, auth=ntlm_handler)
print(f"Estado NTLM: {resultado.status_code}")
print(f"Headers finales: {resultado.headers}")
if __name__ == "__main__":
autenticacion_ntlm()