Sei sulla pagina 1di 40

HOW

TO MAKE
A FULL FLEDGED REST API
with

DJANGO OAUTH TOOLKIT


FEDERICO FRENGUELLI
@synasius


http://evonove.it
GOALS
OAuth2 protected REST API
with Django
WHY?
INTRODUCING

the marvelous

TIMETRACKER
ONCE UPON A TIME...
one tool
single project
deploy once

and everything is ne...


(more or less)
THE TIMES THEY ARE A-CHANGIN'
Web UIs evolve
Smarter Users
Multiple Devices to Support
APPLICATION MITOSIS!
timetracker-backend
timetracker-web
timetracker-android
timetracker-ios
timetracker-desktop (linux, win, osx)

moreover...
SERVICES ARE CONNECTED!
Third party service want your user's data!
WHAT'S IN THE BACKEND?
A service that expose
an amazing and reliable
REST API
THE REAL APP

TIMETRACKER
timetracker-backend
timetracker-web
timetracker-android
timetracker-ios
timetracker-desktop (linux, max, osx)
UI RECIPE
Gumby css framework
Ember.js javascript framework
jQuery

No matter what you use.. it's a pain in the ass!


BACKEND RECIPE
Django
Django REST Framework
Django OAuth Toolkit
MODELS
class Activity(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(blank=True)

class TimeEntry(models.Model):
activity = models.ForeignKey(Activity)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
description = models.TextField(blank=True)
start = models.DateTimeField(blank=True, null=True)
end = models.DateTimeField(blank=True, null=True)
API ENDPOINTS
Url Methods Semantic

/api/activities/ GET, POST list, create

/api/activities/<id>/ GET, PUT/PATCH, DELETE detail, update, remove

/api/tracks/ GET, POST list, create

/api/tracks/<id>/ GET, PUT/PATCH, DELETE detail, update, remove


DEEP INTO DRF
IN 5 MINUTES
SERIALIZE DATA
class ActivitySerializer(serializers.Serializer):
pk = serializers.Field()
name = serializers.CharField(max_length=100)
description = serializers.CharField(required=False)

def restore_object(self, attrs, instance=None):
if instance:
# Update existing instance
instance.name = attrs.get('name', instance.name)
instance.description = attrs.get('description',
instance.description)
return instance

# Create new instance
return Activity(**attrs)

serializer = ActivitySerializer(activity)
serializer.data

# {'pk': 1, 'name': u'Timetracker',


'description': u'Workin on time tracker'}
SIMPLIFY!
MODEL SERIALIZER
class ActivitySerializer(serializers.ModelSerializer):
class Meta:
model = Activity
API ENDPOINTS VIEWS
What do we need?
respect REST semantic
user authentication
permissions checks
(also object level permission)
pagination
response and request formatting

it's a lot of stuff!


KEEP CALM
AND

USE DRF!
SETTINGS
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
),
'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser',
)
}
APIVIEW
class ActivityList(APIView):
""" List all activities, or create a new activity. """
def get(self, request, format=None):
activities = Activity.objects.all()
serializer = ActivitySerializer(activities, many=True)
return Response(serializer.data)

def post(self, request, format=None):


serializer = ActivitySerializer(data=request.DATA)
if serializer.is_valid():
serializer.save()
return Response(serializer.data,
status=status.HTTP_201_CREATED)
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)

urlpatterns = patterns('',
url(r'^api/activities/$', ActivityList.as_view()),
# ...
)
SIMPLIFY!
GENERIC CLASS BASED VIEWS
class ActivityList(generics.ListCreateAPIView):
queryset = Activity.objects.all()
serializer_class = ActivitySerializer

class ActivityDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Activity.objects.all()
serializer_class = ActivitySerializer

class TimeEntryList(generics.ListCreateAPIView):
queryset = TimeEntry.objects.all()
serializer_class = TimeEntrySerializer

class TimeEntryDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = TimeEntry.objects.all()
serializer_class = TimeEntrySerializer
LAZY DEVS?
VIEWSETS
class ActivityViewSet(viewsets.ModelViewSet):
model = Activity

class TimeEntryViewSet(viewsets.ModelViewSet):
model = TimeEntry

router = routers.DefaultRouter()
router.register(r'activities', ActivityViewSet)
router.register(r'tracks', TimeEntryViewSet)

urlpatterns = patterns('',
url(r'^api/', include(router.urls)),
)
BONUS!
BUILTIN BROWSABLE API
HOW DO YOUR CLIENTS AUTHENTICATE?

AND WHAT IF A THIRD PARTY APP


WANTS TO ACCESS YOUR USER'S DATA??
PROBLEMS
Store the user password in the app
The app has a full access to user account
User has to change his password
to revoke the access
Compromised apps expose the user
password

Reference: http://www.slideshare.net/aaronpk/an-introduction-to-oauth2
THE OAUTH2 AUTHORIZATION
FRAMEWORK

How does it work?


USE CASE
ACTORS
Resource Owner: The User
Resource Server: Timetracker API
Authorization Server: The same as the Resource Server
Client: Songify App
STEPS
Client registers with the Authorization Server
The Authorization Server provides client id and client secret
Client directs the Resource Owner to an authorization server
via its user-agent
The Authorization Server authenticates the Resource Owner
and obtains authorization
The Authorization Server directs the Resource Owner back to
the client with the authorization code
The Client exchange the authorization code for a token
The token is used by the Client to authenticate requests
DJANGO OAUTH TOOLKIT
Django 1.4, 1.5, 1.6, 1.7
Python2 & Python3
built on top of oauthlib

https://github.com/evonove/django-oauth-toolkit
DOT AND DJANGO
INSTALLED_APPS += ('oauth2_provider',)

urlpatterns += patterns('',
url(r'^o/', include('oauth2_provider.urls',
namespace='oauth2_provider')),
)

Create a protected endpoint


from oauth2_provider.views.generic import ProtectedResourceView

class ApiEndpoint(ProtectedResourceView):
def get(self, request, *args, **kwargs):
return HttpResponse('Protected with OAuth2!')
BATTERIES INCLUDED
builtin views to register developer apps
form view for user authorization
INTEGRATES WITH DRF
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'oauth2_provider.ext.rest_framework.OAuth2Authentication',
)
}
LET'S TEST IT!
Authorization endpoint
http://localhost:8000/o/authorize?response_type=code&client_id=&redirect_uri=http://exa

Exchange the code


curl -X POST -d "grant_type=authorization_code&code=
&redirect_uri=http://example.com/" http://:@localhost:8000/o/token/

Unauthenticated access
curl http://localhost:8000/api/activities/

Authenticated access
curl -H "Authorization: Bearer " http://localhost:8000/api/activities/
FUTURE PLANS
OAuth1 support
OpenID connector
NoSQL storages support

HELP NEEDED
THANKS