Ok the aim here is to the kerberos/spnego handshake between the browser (basic vue.js app) and the sanic backend.
the handshake should be that:
- browser hits the site (/about in my case)
- sanic should reply with 401 and header www-authenticate: negotiate
- vue/browser should then send the user’s cached creds with header Authorize
the best thing i can find for (2) is the Unauthorized exception.
But for some reason, the configured www-authenticate header is stripped before sending.
I’ve tried following the code progression through the sanic package and i can see the www-authenticate header being added to the exception object, but then when the exception is finally raised it never sends (checked both in browser tools and wireshark to be sure).
server.py looks like this:
from sanic import Sanic
import sanic.response
from sanic.response import text
from sanic_jwt import Initialize, Claim
from classviews.auth.view import authenticate
from classviews.auth import view
from classviews.createtoken.view import Create_Token
from classviews.simpleasyncview.view import SimpleAsyncView
from classviews.simpleview.view import SimpleView
from sanic_ext import Extend
from sanic.log import logger, logging
# add security groups list to token as a claim
class GroupsClaim(Claim):
key = 'groups'
def setup(self, payload, user):
return user.groups
def verify(self, value):
return value == 'bar'
app = Sanic('some_name')
app.config.CORS_ORIGINS = "http://localhost,http://127.0.0.1/"
app.config.CORS_SUPPORTS_CREDENTIALS = "true" #access-control-allow-credentials
app.config.CORS_ALLOW_HEADERS = ['content-type','authorization','www-authenticate'] #access-control-allow-headers
Extend(app)
Initialize(app, authenticate=authenticate, custom_claims=[GroupsClaim])
app.add_route(SimpleView.as_view(), '/')
app.add_route(SimpleAsyncView.as_view(), '/async')
app.add_route(Create_Token.as_view(), '/createtoken')
if __name__ == '__main__':
app.run(host="0.0.0.0", port=7777, debug=True, access_log=True)
and classviews.auth.view looks like this:
from sanic.views import HTTPMethodView
from sanic_jwt import Initialize
from sanic_jwt import exceptions
from sanic import response, text, empty
from sanic.exceptions import Unauthorized, NotFound
class User:
def __init__(self, id, username, password, groups):
self.user_id = id
self.username = username
self.password = password
self.groups = groups
def __repr__(self):
return "User(id='{}')".format(self.user_id)
def to_dict(self):
return {"user_id": self.user_id, "username": self.username}
users = [User(1, "user1", "abcxyz",("group1","group2")), User(2, "user2", "abcxyz",("group1","group3"))]
username_table = {u.username: u for u in users}
userid_table = {u.user_id: u for u in users}
async def authenticate(request, *args, **kwargs):
# perform the kerberos/spnego handshake
# reject with 401 if there is authorization header
if "Authorization" not in request.headers:
#send 401
err = Unauthorized("Auth required hallo.", headers={'www-authenticate': 'negotiate'})
raise err
#return empty(status=401, headers={'www-authenticate': 'negotiate'}) # this gives a 500
username = request.json.get("username", None)
password = request.json.get("password", None)
if not username or not password:
raise exceptions.AuthenticationFailed("Missing username or password.")
user = username_table.get(username, None)
if user is None:
raise exceptions.AuthenticationFailed("User not found.")
if password != user.password:
raise exceptions.AuthenticationFailed("Password is incorrect.")
return user
i’ve read through all the various rfcs etc and i cannot see anything that i might have missed…
not sure where the code is for the sanic web server engine to be able to instrument that and see why the header is never put on the wire…