I am trying to run a standard Python Auth0 project available here
If you are logged in it comes with valid pre-generated keys in .env file and I checked them anyway, so question is similar to Django + Auth0 JWT authentication refusing to decode but answers there do not help.
server.py from example:
import jwt
import base64
import os
from functools import wraps
from flask import Flask, request, jsonify, _request_ctx_stack
from werkzeug.local import LocalProxy
from dotenv import Dotenv
from flask.ext.cors import cross_origin
env = None
try:
env = Dotenv('./.env')
client_id = env["AUTH0_CLIENT_ID"]
client_secret = env["AUTH0_CLIENT_SECRET"]
except IOError:
env = os.environ
app = Flask(__name__)
# Format error response and append status code.
def handle_error(error, status_code):
resp = jsonify(error)
resp.status_code = status_code
return resp
def requires_auth(f):
#wraps(f)
def decorated(*args, **kwargs):
auth = request.headers.get('Authorization', None)
if not auth:
return handle_error({'code': 'authorization_header_missing', 'description': 'Authorization header is expected'}, 401)
parts = auth.split()
if parts[0].lower() != 'bearer':
return handle_error({'code': 'invalid_header', 'description': 'Authorization header must start with Bearer'}, 401)
elif len(parts) == 1:
return handle_error({'code': 'invalid_header', 'description': 'Token not found'}, 401)
elif len(parts) > 2:
return handle_error({'code': 'invalid_header', 'description': 'Authorization header must be Bearer + \s + token'}, 401)
token = parts[1]
try:
payload = jwt.decode(
token,
base64.b64decode(client_secret.replace("_","/").replace("-","+")),
audience=client_id
)
except jwt.ExpiredSignature:
return handle_error({'code': 'token_expired', 'description': 'token is expired'}, 401)
except jwt.InvalidAudienceError:
return handle_error({'code': 'invalid_audience', 'description': 'incorrect audience, expected: ' + client_id}, 401)
except jwt.DecodeError:
return handle_error({'code': 'token_invalid_signature', 'description': 'token signature is invalid'}, 401)
except Exception:
return handle_error({'code': 'invalid_header', 'description':'Unable to parse authentication token.'}, 400)
_request_ctx_stack.top.current_user = user = payload
return f(*args, **kwargs)
return decorated
# Controllers API
#app.route("/ping")
#cross_origin(headers=['Content-Type', 'Authorization'])
def ping():
return "All good. You don't need to be authenticated to call this"
#app.route("/secured/ping")
#cross_origin(headers=['Content-Type', 'Authorization'])
#cross_origin(headers=['Access-Control-Allow-Origin', '*'])
#requires_auth
def securedPing():
return "All good. You only get this message if you're authenticated"
if __name__ == "__main__":
app.run(host='0.0.0.0', port = int(os.environ.get('PORT', 3001)))
http://localhost:3001/secured/ping gives:
{
"code": "authorization_header_missing",
"description": "Authorization header is expected"
}
Headers:
Request URL:http://localhost:3001/secured/ping
Request Method:GET
Status Code:401 UNAUTHORIZED
Remote Address:127.0.0.1:3001
Response Headers
Content-Length:98
Content-Type:application/json
Date:Wed, 15 Jun 2016 13:15:57 GMT
Server:Werkzeug/0.11.4 Python/2.7.6
Request Headers
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding:gzip, deflate, sdch
Accept-Language:en-GB,en-US;q=0.8,en;q=0.6
Cache-Control:max-age=0
Connection:keep-alive
Host:localhost:3001
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/50.0.2661.102 Chrome/50.0.2661.102 Safari/537.36
In the document you linked under the section Call Your API the request is done with the header Authorization for example if you need to connect with curl use the following option:
--header 'Authorization: Bearer YOUR_ID_TOKEN_HERE'
This Authorization header is then parsed by your python code.
But as you can see in your supplied Request Headers there is no Authorization field in the header.
Also, the code sample is not sending any requests but rather serving them, so no changes to the code must be made.
Rather, to request the secured version of ping, you need to request it with one of the methods described in the linked document. Accessing the secured page in a browser is not possible without javascript.
Related
I have a program that Authenticate with API and when logged in search by Id in contacts on this API.
logging in works fine but when I try to find contact this error happen:
401 Client Error: Unauthorized for url:https://api.moxiworks.com/api/contacts/12345678
and same problem happen when try it on Postman like in this image:
after log in I redirected to home route and here is the code:
#app.route('/home', methods=["GET", "POST"])
#login_required
def home():
if request.method == "POST":
found = request.form.get('id')
#base64 encoded Partner ID and Partner Secret
sample_string = ('%s:%s' % (os.getenv("CLIENT_ID"), os.getenv("CLIENT_SECRET"))).replace('\n', '')
sample_string_bytes = sample_string.encode("ascii")
base64_bytes = base64.b64encode(sample_string_bytes)
base64_string = base64_bytes.decode("ascii")
if not found:
return render_template('apology', err='must provide id')
try:
token = session['token']
response = moxi.get(f'https://api.moxiworks.com/api/contacts/{found}',
token=token,
headers={
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic %s' % base64_string,
'Accept': 'application/vnd.moxi-platform+json;version=1',
'Cookie': '_wms_svc_public_session'
})
if response.status_code == 429:
flash('too many requests, wait for 60 seconds then will get your results')
time.sleep(60)
response = moxi.get(f'https://api.moxiworks.com/api/contacts/{found}',
token=token,
headers={
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic %s' % base64_string,
'Accept': 'application/vnd.moxi-platform+json;version=1',
'Cookie': '_wms_svc_public_session'
})
# If the response was successful, no Exception will be raised
response.raise_for_status()
except HTTPError as err:
return render_template('apology.html', err=err)
except Exception as err:
return render_template('apology.html', err=err)
else:
try:
contact = response.json()
return render_template('data.html',
contact1=contact['agent_uuid'], contact2=contact['moxi_works_agent_id'],
contact3=contact['partner_contact_id'], contact4=contact['contact_name'],
contact5=contact['primary_email_address'], contact6=contact['secondary_email_address'],
contact7=contact['primary_phone_number'], contact8=contact['secondary_phone_number'])
except (KeyError, TypeError, ValueError) as err:
return render_template('apology.html', err=err)
else:
return render_template('home.html')
What I miss? or what is wrong in my code?
here is the auth register:
moxi = oauth.register(
name='moxi',
client_id=os.getenv("CLIENT_ID"),
client_secret=os.getenv("CLIENT_SECRET"),
access_token_url='https://sso.moxiworks.com/oauth/token',
access_token_params={'grant_type': 'authorization_code'},
authorize_url='https://sso.moxiworks.com/oauth/authorize',
authorize_params={'response_type': 'code'},
api_base_url='https://api.moxiworks.com/api/contacts/',
userinfo_endpoint='https://sso.moxiworks.com/agent/profile', # This is only needed if using openId to fetch user info
client_kwargs = {
'scope': 'profile',
'token_endpoint_auth_method': 'client_secret_basic',
'token_placement': 'header',
}
)
please help me to figure out how to fix this?
thanks in advance.
The error shows that you have not included your authorisation header. According to the Basic Authentication standard (RFC 7617) used here, you should include the access token in the Authorization header instead of the parameter. As such, it should look something like this enter image description here.
Or on the python code, it will look like this
import requests
url = "https://example.com/api/contacts/1234"
payload = {}
headers = {'Authorization': 'Basic <access_token>'}
response = requests.request("GET", url, headers=headers, data=payload)
print(response.text)
Running into an issue where no requests from OAuth2Session object are being fulfilled.
curl GET "https://api.spotify.com/v1/me/top/artists" -H "Authorization: Bearer <access_token returned from OAuth2Session.access_token within failing program below> works just fine.
The same session that returned that access token returns a 404 on:
session.get('/me/top/artists') [expected, as Authorization: Bearer <access_token> is not passed]
session.request('GET', '/me/top/artists') [reading source seems to imply the Bearer Authorization header is included by default]
Larger code snippet [you'll notice it looks very similar to some of the examples]:
from flask import Flask, request, redirect, render_template, url_for
from rauth.service import OAuth2Service
from app import app
import os
spotify = OAuth2Service(name='spotify',
authorize_url=AUTH_URL,
access_token_url=TOKEN_URL,
client_id=os.getenv('SPOTIFY_CLIENT_ID'),
client_secret=os.getenv('SPOTIFY_CLIENT_SECRET'),
base_url='https://api.spotify.com/v1')
#app.route('/authorize')
def authorize():
redirect_uri = url_for('authorized', _external=True)
params = {'response_type': 'code', 'redirect_uri': redirect_uri, 'scope': 'user-top-read'}
return redirect(spotify.get_authorize_url(**params))
#app.route('/authorized')
def authorized():
if not 'code' in request.args:
flash('You did not authorize the request')
return redirect(url_for('welcome'))
redirect_uri = url_for('authorized', _external=True)
data = {'grant_type': 'authorization_code', 'code': request.args['code'], 'redirect_uri': redirect_uri}
def byte_decoder(byte_load):
return json.loads(byte_load.decode())
session = spotify.get_auth_session(data=data, decoder=byte_decoder)
# this is where I am trying the requests
return redirect(url_for('web'))
AUTH_URL and TOKEN_URL replace links because I don't have 10 reputation to post > 2 links.
I'm trying to use the following test:
def post_webhook(self, payload, **kwargs):
webhook_username = 'test'
webhook_password = 'test'
webhook_url = 'http://{}:{}#localhost:8000/webhook_receive'
webhook_receive = self.app.post(
webhook_url.format(webhook_username, webhook_password),
referrer='http://localhost:8000',
json=payload)
return webhook_receive.status_code
However the main issue is request.authorization is None. Though if I launch the server and use curl -X POST <webhook_url> or requests.post(<webhook_url>), then request.authorization is properly populated.
Trying to figure out the main issue of how to fix this problem.
Using the snippet code and the Flask Test Client, the next pytest code works for me. The way I send the HTTP Basic Auth is the same way curl and HTTPie send it; in the Authorization header with the user and password encoded in base64.
import base64
from app import app
def test_secret_endpoint():
client = app.test_client()
# testing a route without authentication required
rv = client.get('/')
assert rv.status_code == 200
# testing a secured route without credentials
rv = client.post('/secret-page')
assert rv.status_code == 401
# testing a secured route with valid credentials
value = base64.encodestring('admin:secret').replace('\n', '')
headers = {'Authorization': 'Basic {}'.format(value)}
rv = client.post('/secret-page', headers=headers)
assert rv.status_code == 200
assert 'This is secret' in rv.data
The route definitions are:
#app.route('/')
def index():
return 'Hello World :)'
#app.route('/secret-page', methods=['POST'])
#requires_auth
def secret_page():
return 'This is secret'
The request header sending the credentials looks something like this:
POST /secret-page HTTP/1.1
Accept: */*
Authorization: Basic YWRtaW46c2VjcmV0
Connection: keep-alive
Content-Length: 0
Host: localhost:5000
...
Im developing small intranet web service. I want authenticate users over kerberos in MS AD or with basic auth. For that reason i need to set two 'WWW-Authenticate' http headers in response 401. How can i do it with Django ?
Should be something like this:
Client: GET www/index.html
Server: HTTP/1.1 401 Unauthorized
WWW-Authenticate: Negotiate
WWW-Authenticate: Basic realm="corp site"
This code overwrite header
def auth(request):
response = None
auth = request.META.get('HTTP_AUTHORIZATION')
if not auth:
response = HttpResponse(status = 401)
response['WWW-Authenticate'] = 'Negotiate'
response['WWW-Authenticate'] = 'Basic realm=" trolls place basic auth"'
elif auth.startswith('Negotiate YII'):
...
return response
I guess a middleware would be best for this task, but in case you have something else in mind, here is the middleware code adjusted to work with your view(which you can very easily still turn into a middleware if you decide to do so):
from django.conf import settings
from django.http import HttpResponse
def basic_challenge(realm=None):
if realm is None:
realm = getattr(settings,
'WWW_AUTHENTICATION_REALM',
'Restricted Access')
response = HttpResponse('Authorization Required',
mimetype="text/plain")
response['WWW-Authenticate'] = 'Basic realm="%s"' % (realm)
response.status_code = 401
return response
def basic_authenticate(authentication):
(authmeth, auth) = authentication.split(' ', 1)
if 'basic' != authmeth.lower():
return None
auth = auth.strip().decode('base64')
username, password = auth.split(':', 1)
AUTHENTICATION_USERNAME = getattr(settings,
'BASIC_WWW_AUTHENTICATION_USERNAME')
AUTHENTICATION_PASSWORD = getattr(settings,
'BASIC_WWW_AUTHENTICATION_PASSWORD')
return (username == AUTHENTICATION_USERNAME and
password == AUTHENTICATION_PASSWORD)
def auth_view(request):
auth = request.META.get('HTTP_AUTHORIZATION', '')
if auth.startswith('Negotiate YII'):
pass
elif auth:
if basic_authenticate(auth):
#successfully authenticated
pass
else:
return basic_challenge()
# nothing matched, still return basic challange
return basic_challenge()
I'm trying to issue a basic UbuntuOne API call.
As explained on https://one.ubuntu.com/developer/account_admin/auth/otherplatforms, I'm getting the OAUTH token and then passing it to the UbuntuOne service.
I get the token and consumer info alright
I'm then trying to issue a /api/file_storage/v1 API call (see: https://one.ubuntu.com/developer/files/store_files/cloud.) The request is signed using the OAUTH token.
The code snippet below is the exact code I'm executing (minus the email.password/description fields.) The token and consumer data is returned properly. I'm getting a '401 UNAUTHORIZED' from the server when issuing the /api/file_storage/v1 request... any idea why?
import base64
import json
import urllib
import urllib2
import oauth2
email = 'bla'
password = 'foo'
description = 'bar'
class Unauthorized(Exception):
"""The provided email address and password were incorrect."""
def acquire_token(email_address, password, description):
"""Aquire an OAuth access token for the given user."""
# Issue a new access token for the user.
request = urllib2.Request(
'https://login.ubuntu.com/api/1.0/authentications?' +
urllib.urlencode({'ws.op': 'authenticate', 'token_name': description}))
request.add_header('Accept', 'application/json')
request.add_header('Authorization', 'Basic %s' % base64.b64encode('%s:%s' % (email_address, password)))
try:
response = urllib2.urlopen(request)
except urllib2.HTTPError, exc:
if exc.code == 401: # Unauthorized
raise Unauthorized("Bad email address or password")
else:
raise
data = json.load(response)
consumer = oauth2.Consumer(data['consumer_key'], data['consumer_secret'])
token = oauth2.Token(data['token'], data['token_secret'])
# Tell Ubuntu One about the new token.
get_tokens_url = ('https://one.ubuntu.com/oauth/sso-finished-so-get-tokens/')
oauth_request = oauth2.Request.from_consumer_and_token(consumer, token, 'GET', get_tokens_url)
oauth_request.sign_request(oauth2.SignatureMethod_PLAINTEXT(), consumer, token)
request = urllib2.Request(get_tokens_url)
for header, value in oauth_request.to_header().items():
request.add_header(header, value)
response = urllib2.urlopen(request)
return consumer, token
if __name__ == '__main__':
consumer, token = acquire_token(email, password, description)
print 'Consumer:', consumer
print 'Token:', token
url = 'https://one.ubuntu.com/api/file_storage/v1'
oauth_request = oauth2.Request.from_consumer_and_token(consumer, token, 'GET', url)
oauth_request.sign_request(oauth2.SignatureMethod_PLAINTEXT(), consumer, token)
request = urllib2.Request(url)
request.add_header('Accept', 'application/json')
for header, value in oauth_request.to_header().items():
request.add_header(header, value)
response = urllib2.urlopen(request)
The issue was with the 'description' field. It must be in the following format:
Ubuntu One # $hostname [$application]
Else, the UbuntuOne service returns a "ok 0/1" and does not register the token.