Skip to content

Commit f412b0b

Browse files
authored
Add getting started documentation section (#841)
1 parent 96383d9 commit f412b0b

File tree

6 files changed

+397
-1
lines changed

6 files changed

+397
-1
lines changed
17 KB
Loading
36.2 KB
Loading
32.7 KB
Loading

docs/getting_started.rst

Lines changed: 394 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,394 @@
1+
Getting started
2+
===============
3+
4+
Build a OAuth2 provider using Django, Django OAuth Toolkit, and OAuthLib.
5+
6+
What we will build?
7+
-------------------
8+
9+
The plan is to build an OAuth2 provider from ground up.
10+
11+
On this getting started we will:
12+
13+
* Create the Django project.
14+
* Install and configure Django OAuth Toolkit.
15+
* Create two OAuth2 applications.
16+
* Use Authorization code grant flow.
17+
* Use Client Credential grant flow.
18+
19+
What is OAuth?
20+
----------------
21+
22+
OAuth is an open standard for access delegation, commonly used as a way for Internet users to grant websites or applications access to their information on other websites but without giving them the passwords.
23+
-- `Whitson Gordon`_
24+
25+
Django
26+
------
27+
28+
Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design. Built by experienced developers, it takes care of much of the hassle of Web development, so you can focus on writing your app without needing to reinvent the wheel.
29+
-- `Django website`_
30+
31+
Let's get start by creating a virtual environment::
32+
33+
mkproject iam
34+
35+
This will create, activate and change directory to the new Python virtual environment.
36+
37+
Install Django::
38+
39+
pip install Django
40+
41+
Create a Django project::
42+
43+
django-admin startproject iam
44+
45+
This will create a mysite directory in your current directory. With the following estructure::
46+
47+
.
48+
└── iam
49+
├── iam
50+
│   ├── asgi.py
51+
│   ├── __init__.py
52+
│   ├── settings.py
53+
│   ├── urls.py
54+
│   └── wsgi.py
55+
└── manage.py
56+
57+
Create a Django application::
58+
59+
cd iam/
60+
python manage.py startapp users
61+
62+
That’ll create a directory :file:`users`, which is laid out like this::
63+
64+
.
65+
├── iam
66+
│   ├── asgi.py
67+
│   ├── __init__.py
68+
│   ├── settings.py
69+
│   ├── urls.py
70+
│   └── wsgi.py
71+
├── manage.py
72+
└── users
73+
├── admin.py
74+
├── apps.py
75+
├── __init__.py
76+
├── migrations
77+
│   └── __init__.py
78+
├── models.py
79+
├── tests.py
80+
└── views.py
81+
82+
If you’re starting a new project, it’s highly recommended to set up a custom user model, even if the default `User`_ model is sufficient for you. This model behaves identically to the default user model, but you’ll be able to customize it in the future if the need arises.
83+
-- `Django documentation`_
84+
85+
Edit :file:`users/models.py` adding the code bellow:
86+
87+
.. code-block:: python
88+
89+
from django.contrib.auth.models import AbstractUser
90+
91+
class User(AbstractUser):
92+
pass
93+
94+
Change :file:`iam/settings.py` to add ``users`` application to ``INSTALLED_APPS``:
95+
96+
.. code-block:: python
97+
98+
INSTALLED_APPS = [
99+
'django.contrib.admin',
100+
'django.contrib.auth',
101+
'django.contrib.contenttypes',
102+
'django.contrib.sessions',
103+
'django.contrib.messages',
104+
'django.contrib.staticfiles',
105+
'users',
106+
]
107+
108+
Configure ``users.User`` to be the model used for the ``auth`` application adding ``AUTH_USER_MODEL`` to :file:`iam/settings.py`:
109+
110+
.. code-block:: python
111+
112+
AUTH_USER_MODEL='users.User'
113+
114+
Create inital migration for ``users`` application ``User`` model::
115+
116+
python manage.py makemigrations
117+
118+
The command above will create the migration::
119+
120+
Migrations for 'users':
121+
users/migrations/0001_initial.py
122+
- Create model User
123+
124+
Finally execute the migration::
125+
126+
python manage.py migrate
127+
128+
The ``migrate`` output::
129+
130+
Operations to perform:
131+
Apply all migrations: admin, auth, contenttypes, sessions, users
132+
Running migrations:
133+
Applying contenttypes.0001_initial... OK
134+
Applying contenttypes.0002_remove_content_type_name... OK
135+
Applying auth.0001_initial... OK
136+
Applying auth.0002_alter_permission_name_max_length... OK
137+
Applying auth.0003_alter_user_email_max_length... OK
138+
Applying auth.0004_alter_user_username_opts... OK
139+
Applying auth.0005_alter_user_last_login_null... OK
140+
Applying auth.0006_require_contenttypes_0002... OK
141+
Applying auth.0007_alter_validators_add_error_messages... OK
142+
Applying auth.0008_alter_user_username_max_length... OK
143+
Applying auth.0009_alter_user_last_name_max_length... OK
144+
Applying auth.0010_alter_group_name_max_length... OK
145+
Applying auth.0011_update_proxy_permissions... OK
146+
Applying users.0001_initial... OK
147+
Applying admin.0001_initial... OK
148+
Applying admin.0002_logentry_remove_auto_add... OK
149+
Applying admin.0003_logentry_add_action_flag_choices... OK
150+
Applying sessions.0001_initial... OK
151+
152+
Django OAuth Toolkit
153+
--------------------
154+
155+
Django OAuth Toolkit can help you providing out of the box all the endpoints, data and logic needed to add OAuth2 capabilities to your Django projects.
156+
157+
Install Django OAuth Toolkit::
158+
159+
pip install django-oauth-toolkit
160+
161+
Add ``oauth2_provider`` to ``INSTALLED_APPS`` in :file:`iam/settings.py`:
162+
163+
.. code-block:: python
164+
165+
INSTALLED_APPS = [
166+
'django.contrib.admin',
167+
'django.contrib.auth',
168+
'django.contrib.contenttypes',
169+
'django.contrib.sessions',
170+
'django.contrib.messages',
171+
'django.contrib.staticfiles',
172+
'users',
173+
'oauth2_provider',
174+
]
175+
176+
Execute the migration::
177+
178+
python manage.py migrate
179+
180+
The ``migrate`` command output::
181+
182+
Operations to perform:
183+
Apply all migrations: admin, auth, contenttypes, oauth2_provider, sessions, users
184+
Running migrations:
185+
Applying oauth2_provider.0001_initial... OK
186+
Applying oauth2_provider.0002_auto_20190406_1805... OK
187+
188+
Include ``oauth2_provider.urls`` to :file:`iam/urls.py` as follows:
189+
190+
.. code-block:: python
191+
192+
from django.contrib import admin
193+
from django.urls import include, path
194+
195+
urlpatterns = [
196+
path('admin/', admin.site.urls),
197+
path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
198+
]
199+
200+
This will make available endpoints to authorize, generate token and create OAuth applications.
201+
202+
Last change, add ``LOGIN_URL`` to :file:`iam/settings.py`:
203+
204+
.. code-block:: python
205+
206+
LOGIN_URL='/admin/login/'
207+
208+
We will use Django Admin login to make our life easy.
209+
210+
Create a user::
211+
212+
python manage.py createsuperuser
213+
214+
Username: wiliam
215+
Email address: [email protected]
216+
Password:
217+
Password (again):
218+
Superuser created successfully.
219+
220+
OAuth2 Authorization Grants
221+
---------------------------
222+
223+
An authorization grant is a credential representing the resource owner's authorization (to access its protected resources) used by the client to obtain an access token.
224+
-- `RFC6749`_
225+
226+
The OAuth framework specifies several grant types for different use cases.
227+
-- `Grant types`_
228+
229+
We will start by given a try to the grant types listed below:
230+
231+
* Authorization code
232+
* Client credential
233+
234+
This two grant types cover the most initially used uses cases.
235+
236+
Authorization Code
237+
------------------
238+
239+
The Authorization Code flow is best used in web and mobile apps. This is the flow used for third party integration, the user authorize your partner to access its products in your APIs.
240+
241+
Start the development server::
242+
243+
python manage.py runserver
244+
245+
Point your browser to http://127.0.0.1:8000/o/applications/register/ lets create an application.
246+
247+
Fill the form as show in the screenshot bellow and before save take note of ``Client id`` and ``Client secret`` we will use it in a minute.
248+
249+
.. image:: _images/application-register-auth-code.png
250+
:alt: Authorization code application registration
251+
252+
Export ``Client id`` and ``Client secret`` values as environment variable:
253+
254+
.. sourcecode:: sh
255+
256+
export ID=vW1RcAl7Mb0d5gyHNQIAcH110lWoOW2BmWJIero8
257+
export SECRET=DZFpuNjRdt5xUEzxXovAp40bU3lQvoMvF3awEStn61RXWE0Ses4RgzHWKJKTvUCHfRkhcBi3ebsEfSjfEO96vo2Sh6pZlxJ6f7KcUbhvqMMPoVxRwv4vfdWEoWMGPeIO
258+
259+
To start the Authorization code flow got to this `URL`_ with is the same as show bellow::
260+
261+
http://127.0.0.1:8000/o/authorize/?response_type=code&client_id=vW1RcAl7Mb0d5gyHNQIAcH110lWoOW2BmWJIero8&redirect_uri=http://127.0.0.1:8000/noexist/callback
262+
263+
Note the parameters we pass:
264+
265+
* **response_type**: ``code``
266+
* **client_id**: ``vW1RcAl7Mb0d5gyHNQIAcH110lWoOW2BmWJIero8``
267+
* **redirect_uri**: ``http://127.0.0.1:8000/noexist/callback``
268+
269+
This identifies your application, the user is asked to authorize your application to access its resources.
270+
271+
Go ahead and authorize the ``web-app``
272+
273+
.. image:: _images/application-authorize-web-app.png
274+
:alt: Authorization code authorize web-app
275+
276+
Remenber we used ``http://127.0.0.1:8000/noexist/callback`` as ``redirect_uri`` you will get a **Page not found (404)** but it worked if you get a url like::
277+
278+
http://127.0.0.1:8000/noexist/callback?code=uVqLxiHDKIirldDZQfSnDsmYW1Abj2
279+
280+
This is the OAuth2 provider trying to give you a ``code`` in this case ``uVqLxiHDKIirldDZQfSnDsmYW1Abj2``.
281+
282+
Export it as environment variable:
283+
284+
.. code-block:: sh
285+
286+
export CODE=uVqLxiHDKIirldDZQfSnDsmYW1Abj2
287+
288+
Now that you have the user authorization is time to get an access token::
289+
290+
curl -X POST -H "Cache-Control: no-cache" -H "Content-Type: application/x-www-form-urlencoded" "http://127.0.0.1:8000/o/token/" -d "client_id=${ID}" -d "client_secret=${SECRET}" -d "code=${CODE}" -d "redirect_uri=http://127.0.0.1:8000/noexist/callback" -d "grant_type=authorization_code"
291+
292+
To be more easy to visualize::
293+
294+
curl -X POST \
295+
-H "Cache-Control: no-cache" \
296+
-H "Content-Type: application/x-www-form-urlencoded" \
297+
"http://127.0.0.1:8000/o/token/" \
298+
-d "client_id=${ID}" \
299+
-d "client_secret=${SECRET}" \
300+
-d "code=${CODE}" \
301+
-d "redirect_uri=http://127.0.0.1:8000/noexist/callback" \
302+
-d "grant_type=authorization_code"
303+
304+
The OAuth2 provider will return the follow response:
305+
306+
.. code-block:: javascript
307+
308+
{
309+
"access_token": "jooqrnOrNa0BrNWlg68u9sl6SkdFZg",
310+
"expires_in": 36000,
311+
"token_type": "Bearer",
312+
"scope": "read write",
313+
"refresh_token": "HNvDQjjsnvDySaK0miwG4lttJEl9yD"
314+
}
315+
316+
To access the user resources we just use the ``access_token``::
317+
318+
curl \
319+
-H "Authorization: Bearer jooqrnOrNa0BrNWlg68u9sl6SkdFZg" \
320+
-X GET http://localhost:8000/resource
321+
322+
Client Credential
323+
-----------------
324+
325+
The Client Credential grant is suitable for machine-to-machine authentication. You authorize your own service or worker to change a bank account transaction status to accepted.
326+
327+
Point your browser to http://127.0.0.1:8000/o/applications/register/ lets create an application.
328+
329+
Fill the form as show in the screenshot bellow and before save take note of ``Client id`` and ``Client secret`` we will use it in a minute.
330+
331+
.. image:: _images/application-register-client-credential.png
332+
:alt: Client credential application registration
333+
334+
Export ``Client id`` and ``Client secret`` values as environment variable:
335+
336+
.. code-block:: sh
337+
338+
export ID=axXSSBVuvOyGVzh4PurvKaq5MHXMm7FtrHgDMi4u
339+
export SECRET=1fuv5WVfR7A5BlF0o155H7s5bLgXlwWLhi3Y7pdJ9aJuCdl0XV5Cxgd0tri7nSzC80qyrovh8qFXFHgFAAc0ldPNn5ZYLanxSm1SI1rxlRrWUP591wpHDGa3pSpB6dCZ
340+
341+
The Client Credential flow is simpler than the Authorization Code flow.
342+
343+
We need to encode ``client_id`` and ``client_secret`` as HTTP base authentication encoded in ``base64`` I use the following code to do that.
344+
345+
.. code-block:: python
346+
347+
>>> import base64
348+
>>> client_id = "axXSSBVuvOyGVzh4PurvKaq5MHXMm7FtrHgDMi4u"
349+
>>> secret = "1fuv5WVfR7A5BlF0o155H7s5bLgXlwWLhi3Y7pdJ9aJuCdl0XV5Cxgd0tri7nSzC80qyrovh8qFXFHgFAAc0ldPNn5ZYLanxSm1SI1rxlRrWUP591wpHDGa3pSpB6dCZ"
350+
>>> credential = "{0}:{1}".format(client_id, secret)
351+
>>> base64.b64encode(credential.encode("utf-8"))
352+
b'YXhYU1NCVnV2T3lHVnpoNFB1cnZLYXE1TUhYTW03RnRySGdETWk0dToxZnV2NVdWZlI3QTVCbEYwbzE1NUg3czViTGdYbHdXTGhpM1k3cGRKOWFKdUNkbDBYVjVDeGdkMHRyaTduU3pDODBxeXJvdmg4cUZYRkhnRkFBYzBsZFBObjVaWUxhbnhTbTFTSTFyeGxScldVUDU5MXdwSERHYTNwU3BCNmRDWg=='
353+
>>>
354+
355+
Export the credential as environment variable
356+
357+
.. code-block:: sh
358+
359+
export CREDENTIAL=YXhYU1NCVnV2T3lHVnpoNFB1cnZLYXE1TUhYTW03RnRySGdETWk0dToxZnV2NVdWZlI3QTVCbEYwbzE1NUg3czViTGdYbHdXTGhpM1k3cGRKOWFKdUNkbDBYVjVDeGdkMHRyaTduU3pDODBxeXJvdmg4cUZYRkhnRkFBYzBsZFBObjVaWUxhbnhTbTFTSTFyeGxScldVUDU5MXdwSERHYTNwU3BCNmRDWg==
360+
361+
To start the Client Credential flow you call ``/token/`` endpoint direct::
362+
363+
curl -X POST -H "Authorization: Basic ${CREDENTIAL}" -H "Cache-Control: no-cache" -H "Content-Type: application/x-www-form-urlencoded" "http://127.0.0.1:8000/o/token/" -d "grant_type=client_credentials"
364+
365+
To be more easy to visualize::
366+
367+
curl -X POST \
368+
-H "Authorization: Basic ${CREDENTIAL}" \
369+
-H "Cache-Control: no-cache" \
370+
-H "Content-Type: application/x-www-form-urlencoded" \
371+
"http://127.0.0.1:8000/o/token/" \
372+
-d "grant_type=client_credentials"
373+
374+
The OAuth2 provider will return the follow response:
375+
376+
.. code-block:: javascript
377+
378+
{
379+
"access_token": "PaZDOD5UwzbGOFsQr34LQ7JUYOj3yK",
380+
"expires_in": 36000,
381+
"token_type": "Bearer",
382+
"scope": "read write"
383+
}
384+
385+
Next step is :doc:`first tutorial <tutorial/tutorial_01>`.
386+
387+
.. _Django website: https://www.djangoproject.com/
388+
.. _Whitson Gordon: https://en.wikipedia.org/wiki/OAuth#cite_note-1
389+
.. _User: https://docs.djangoproject.com/en/3.0/ref/contrib/auth/#django.contrib.auth.models.User
390+
.. _Django documentation: https://docs.djangoproject.com/en/3.0/topics/auth/customizing/#using-a-custom-user-model-when-starting-a-project
391+
.. _RFC6749: https://tools.ietf.org/html/rfc6749#section-1.3
392+
.. _Grant Types: https://oauth.net/2/grant-types/
393+
.. _URL: http://127.0.0.1:8000/o/authorize/?response_type=code&client_id=vW1RcAl7Mb0d5gyHNQIAcH110lWoOW2BmWJIero8&redirect_uri=http://127.0.0.1:8000/noexist/callback
394+

0 commit comments

Comments
 (0)