Skip to content

Instantly share code, notes, and snippets.

@drgarcia1986
Last active August 9, 2023 21:20
Show Gist options
  • Star 36 You must be signed in to star a gist
  • Fork 16 You must be signed in to fork a gist
  • Save drgarcia1986/5dbd7ce85fb2db74a51b to your computer and use it in GitHub Desktop.
Save drgarcia1986/5dbd7ce85fb2db74a51b to your computer and use it in GitHub Desktop.
Example of OAuth2 autentication server with Client Credentials grant (using python-oauth2 and tornado)
# !/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'Diego Garcia'
import tornado.web
import tornado.ioloop
import oauth2.tokengenerator
import oauth2.grant
import oauth2.store.redisdb
import oauth2.store.mongodb
import json
import time
import fakeredis
import mongomock
class OAuth2Handler(tornado.web.RequestHandler):
# Generator of tokens (with client authentications)
def initialize(self, controller):
self.controller = controller
def post(self):
response = self._dispatch_request()
self._map_response(response)
def _dispatch_request(self):
request = self.request
request.post_param = lambda key: json.loads(request.body.decode())[key]
return self.controller.dispatch(request, environ={})
def _map_response(self, response):
for name, value in list(response.headers.items()):
self.set_header(name, value)
self.set_status(response.status_code)
self.write(response.body)
class BaseHandler(tornado.web.RequestHandler):
def initialize(self, controller):
self.controller = controller
# authenticate tokens
def prepare(self):
try:
token = self.get_argument('access_token', None)
if not token:
auth_header = self.request.headers.get('Authorization', None)
if not auth_header:
raise Exception('This resource need a authorization token')
token = auth_header[7:]
key = 'oauth2_{}'.format(token)
access = self.controller.access_token_store.rs.get(key)
if access:
access = json.loads(access.decode())
else:
raise Exception('Invalid Token')
if access['expires_at'] <= int(time.time()):
raise Exception('expired token')
except Exception as err:
self.set_header('Content-Type', 'application/json')
self.set_status(401)
self.finish(json.dumps({'error': str(err)}))
class FooHandler(BaseHandler):
def get(self):
self.finish(json.dumps({'msg': 'This is Foo!'}))
def main():
# Populate mock
mongo = mongomock.MongoClient()
mongo['db']['oauth_clients'].insert({'identifier': 'abc',
'secret': 'xyz',
'redirect_uris': [],
'authorized_grants': [oauth2.grant.ClientCredentialsGrant.grant_type]})
# MongoDB for clients store
client_store = oauth2.store.mongodb.ClientStore(mongo['db']['oauth_clients'])
# Redis for tokens storage
token_store = oauth2.store.redisdb.TokenStore(rs=fakeredis.FakeStrictRedis())
# Generator of tokens
token_generator = oauth2.tokengenerator.Uuid4()
token_generator.expires_in[oauth2.grant.ClientCredentialsGrant.grant_type] = 3600
# OAuth2 controller
auth_controller = oauth2.Provider(
access_token_store=token_store,
auth_code_store=token_store,
client_store=client_store,
site_adapter=None,
token_generator=token_generator
)
auth_controller.token_path = '/oauth/token'
# Add Client Credentials to OAuth2 controller
auth_controller.add_grant(oauth2.grant.ClientCredentialsGrant())
# Create Tornado application
app = tornado.web.Application([
(r'/oauth/token', OAuth2Handler, dict(controller=auth_controller)),
(r'/foo', FooHandler, dict(controller=auth_controller))
])
# Start Server
app.listen(8889)
print("Server Starting")
tornado.ioloop.IOLoop.instance().start()
if __name__ == "__main__":
main()

Client Step By Step

Get a Token

Request

curl -i http://localhost:8889/oauth/token -X POST -H 'Content-Type: application/json' -d '{"client_id": "abc", "client_secret": "xyz", "grant_type": "client_credentials", "scope": "foo"}'

Response

HTTP/1.1 200 OK
Content-Length: 100
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
Date: Tue, 18 Nov 2014 22:32:18 GMT
Server: TornadoServer/4.0.2
Proxy-Connection: keep-alive
Connection: keep-alive

{"token_type": "Bearer", "expires_in": 3600, "access_token": "7d2adcd2-2756-4531-b7d2-69c19f5b1063"}

Consumer Resource

Request

curl -i http://localhost:8889/foo -H 'Authorization: Bearer 7d2adcd2-2756-4531-b7d2-69c19f5b1063'

Response

HTTP/1.1 200 OK
Etag: "e8ac30e7653f247f956a04b1f901d893e593cd1b"
Content-Length: 23
Date: Tue, 18 Nov 2014 22:33:15 GMT
Content-Type: text/html; charset=UTF-8
Server: TornadoServer/4.0.2
Proxy-Connection: keep-alive
Connection: keep-alive

{"msg": "This is Foo!"}
tornado
python-oauth2
redis
fakeredis
mongomock
pymongo
@bluzky
Copy link

bluzky commented Apr 29, 2015

Thanks for your example

@rmujica
Copy link

rmujica commented Nov 2, 2015

thank you very much!!

@apoclyps
Copy link

Can you specify your versions you used requirements.txt please? I'm seeing the following error whilst attempting to run this example.

Traceback (most recent call last):
File "main.py", line 117, in
main()
File "main.py", line 97, in main
token_generator=token_generator
TypeError: init() got an unexpected keyword argument 'site_adapter'

@ubeerli
Copy link

ubeerli commented Nov 9, 2016

@apoclyps: I am experiencing the same error, could you solve it?

@danigosa
Copy link

Just remove the parameter when creating the object, recent documentation don't use it: https://github.com/wndhydrnt/python-oauth2

@dannybritto96
Copy link

I'm getting CORS error when I use handlers with OAuth. Without authentication and the proper header set, the API works fine. I suppose CORS issue occurs at when the token is getting authorized. Any ideas on how to fix this?

@rriggio
Copy link

rriggio commented Oct 27, 2019

how do you grant the token on a per URL basis?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment