I have simple Twisted-Klein
server, with HTTP Basic Auth enabled globally:
from klein import Klein
import attr
from zope.interface import implementer
from twisted.cred.portal import IRealm
from twisted.internet.defer import succeed
from twisted.cred.portal import Portal
from twisted.cred.checkers import FilePasswordDB
from twisted.web.resource import IResource
from twisted.web.guard import HTTPAuthSessionWrapper, BasicCredentialFactory
from werkzeug.datastructures import MultiDict
from bson import json_util
import json
app = Klein()
# health check
@app.route('/health', methods=['GET'])
def health_check(request):
return ''
# dataset query API
@app.route('/query/<path:expression>', methods=['GET'])
def query(request, expression):
response = evaluate_expression(expression)
return response
@implementer(IRealm)
@attr.s
class HTTPAuthRealm(object):
resource = attr.ib()
def requestAvatar(self, avatarId, mind, *interfaces):
return succeed((IResource, self.resource, lambda: None))
def resource():
realm = HTTPAuthRealm(resource=app.resource())
portal = Portal(realm, [FilePasswordDB('./configs/server-auth.db')])
credential_factory = BasicCredentialFactory('Authentication required')
return HTTPAuthSessionWrapper(portal, [credential_factory])
I want to disable auth for only specific API endpoints, for example, in this case for /health
API endpoint. I've read the docs, but just cant wrap my mind around it.
One way is to only wrap the part of the hierarchy that you want authentication for:
from twisted.web.resource import Resource
class Health(Resource):
# ...
def resource():
realm = HTTPAuthRealm(resource=app.resource())
portal = Portal(realm, [FilePasswordDB('./configs/server-auth.db')])
credential_factory = BasicCredentialFactory('Authentication required')
guarded = HTTPAuthSessionWrapper(portal, [credential_factory])
root = Resource()
root.putChild(b"health", Health())
root.putChild(b"this-stuff-requires-auth", guarded)
return root
The normal resource traversal logic used for dispatching requests will start at root
. If the request is for /health
(or any child) then it goes to root
's health
child - which is the Health
instance created in this example. Note how the HTTPAuthSessionWrapper
doesn't get involved there. If the request is for /this-stuff-requires-auth
(or any child) then traversal does go through the auth wrapper and so authentication is required.
Another approach is to vary your avatar based on the credentials. In this scheme, you actually still authenticate everyone but you authorize anonymous users to access some of the hierarchy.
from twisted.cred.checkers import ANONYMOUS
@implementer(IRealm)
@attr.s
class HTTPAuthRealm(object):
def requestAvatar(self, avatarId, mind, *interfaces):
avatar = Resource()
avatar.putChild(b"health", Health())
if avatarId is not ANONYMOUS:
avatar.putChild(b"this-stuff-requires-auth", SecretResource())
return succeed((IResource, avatar, lambda: None))
You'll also need to configure your portal with a credentials checker for anonymous credentials:
from twisted.cred.checkers import AllowAnonymousAccess
portal = Portal(
realm, [
FilePasswordDB('./configs/server-auth.db'),
AllowAnonymousAccess(),
],
)
In this approach, HTTPAuthSessionWrapper
is again your root resource.
Anonymous requests are associated with the ANONYMOUS
avatar identifier and HTTPAuthRealm
gives back an IResource
which only knows about the resources that should be available to anonymous users.
Requests with valid user credentials are associated with a different avatar identifier (usually their username) and HTTPAuthRealm
gives back an IResource
with more children attached to it, granting more access.