├── api
├── migrations
│ └── __init__.py
├── __init__.py
├── admin.py
├── tests.py
├── models.py
├── apps.py
├── serializers.py
├── urls.py
├── signals.py
└── views.py
├── django_react_auth
├── __init__.py
├── wsgi.py
├── urls.py
└── settings.py
├── requirements.txt
├── db.sqlite3
├── .gitignore
├── templates
└── index.html
├── manage.py
├── README.md
├── assets
└── js
│ ├── index.jsx
│ ├── login.jsx
│ ├── app.jsx
│ └── auth.js
├── package.json
└── webpack.config.js
/api/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/django_react_auth/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/api/__init__.py:
--------------------------------------------------------------------------------
1 | default_app_config = 'api.apps.ApiConfig'
2 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Django==1.9.4
2 | djangorestframework==3.3.3
3 |
--------------------------------------------------------------------------------
/db.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coxjonc/django-react-auth/HEAD/db.sqlite3
--------------------------------------------------------------------------------
/api/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | # Register your models here.
4 |
--------------------------------------------------------------------------------
/api/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | *.pyc
3 | node_modules/
4 |
5 | # python virtual environment
6 | env/
7 |
--------------------------------------------------------------------------------
/api/models.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | from django.db import models
4 |
5 | # Create your models here.
6 |
--------------------------------------------------------------------------------
/api/apps.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | from django.apps import AppConfig
4 |
5 |
6 | class ApiConfig(AppConfig):
7 | name = 'api'
8 |
9 | def ready(self):
10 | from . import signals
11 |
--------------------------------------------------------------------------------
/api/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 |
3 | from django.contrib.auth.models import User
4 |
5 | class UserSerializer(serializers.ModelSerializer):
6 | class Meta:
7 | model = User
8 | fields = ('username',)
9 |
--------------------------------------------------------------------------------
/templates/index.html:
--------------------------------------------------------------------------------
1 | {% load staticfiles %}
2 |
3 |
4 |
5 |
6 | Django React Auth
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_react_auth.settings")
7 |
8 | from django.core.management import execute_from_command_line
9 |
10 | execute_from_command_line(sys.argv)
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Token-based Authentication with React and DRF
2 | ---------------------------------------------
3 | This is an example project for [my tutorial on authenticating users with React and Django](http://geezhawk.github.io/user-authentication-with-react-and-django-rest-framework). To test it out yourself, clone the repository and run:
4 |
5 | 1. `pip install -Ur requirements.txt`
6 | 2. `npm install`
7 | 3. `npm run build`
8 | 4. `python manage.py runserver`
9 |
--------------------------------------------------------------------------------
/api/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import url
2 | from django.views.decorators.csrf import csrf_exempt
3 |
4 | from rest_framework.routers import DefaultRouter
5 | from rest_framework.authtoken.views import obtain_auth_token
6 |
7 | from .views import UserViewSet
8 |
9 | router = DefaultRouter()
10 | router.register(r'users', UserViewSet)
11 |
12 | urlpatterns = router.urls
13 |
14 | urlpatterns += [
15 | url(r'^obtain-auth-token/$', csrf_exempt(obtain_auth_token))
16 | ]
17 |
--------------------------------------------------------------------------------
/django_react_auth/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for django_react_auth project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_react_auth.settings")
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/api/signals.py:
--------------------------------------------------------------------------------
1 | from django.db.models.signals import post_save
2 | from django.contrib.auth.models import User
3 | from django.dispatch import receiver
4 |
5 | from rest_framework.authtoken.models import Token
6 |
7 | @receiver(post_save, sender=User)
8 | def init_new_user(sender, instance, signal, created, **kwargs):
9 | """
10 | Create an authentication token for new users
11 | """
12 | if created:
13 | print('Token created')
14 | Token.objects.create(user=instance)
15 |
--------------------------------------------------------------------------------
/api/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.models import User
2 |
3 | from rest_framework import viewsets, response, permissions
4 |
5 | from .serializers import UserSerializer
6 |
7 | class UserViewSet(viewsets.ModelViewSet):
8 | queryset = User.objects.all()
9 | serializer_class = UserSerializer
10 | permission_classes = (permissions.IsAuthenticated,)
11 |
12 | def retrieve(self, request, pk=None):
13 | if pk == 'i':
14 | return response.Response(UserSerializer(request.user,
15 | context={'request':request}).data)
16 | return super(UserViewSet, self).retrieve(request, pk)
17 |
--------------------------------------------------------------------------------
/assets/js/index.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react')
2 | var ReactDOM = require('react-dom')
3 | var Router = require('react-router')
4 | var App = require('./app')
5 | var Login = require('./login')
6 | var auth = require('./auth')
7 |
8 | function requireAuth(nextState, replace) {
9 | if (!auth.loggedIn()) {
10 | replace({
11 | pathname:'/app/login/',
12 | state: {nextPathname: '/app/'}
13 | })
14 | }
15 | }
16 |
17 | ReactDOM.render(
18 |
19 |
20 |
21 | ,
22 | document.getElementById('app')
23 | )
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "django_react_auth",
3 | "version": "1.0.0",
4 | "description": "Simple authentication with Django and React",
5 | "main": "webpack.config.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "webpack --config webpack.config.js"
9 | },
10 | "keywords": [
11 | "react",
12 | "django",
13 | "webpack",
14 | "authentication"
15 | ],
16 | "author": "Jonathan Cox",
17 | "license": "GPL-3.0",
18 | "dependencies": {
19 | "babel-core": "^6.7.4",
20 | "babel-loader": "^6.2.4",
21 | "babel-preset-es2016": "^6.0.6",
22 | "babel-preset-react": "^6.5.0",
23 | "babel-runtime": "^6.6.1",
24 | "jquery": "^2.2.2",
25 | "react": "^0.14.7",
26 | "react-dom": "^0.14.7",
27 | "react-router": "^2.0.1",
28 | "webpack": "^1.12.14",
29 | "webpack-bundle-tracker": "0.0.93"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var webpack = require('webpack')
3 |
4 | module.exports = {
5 | context: __dirname,
6 |
7 | entry: './assets/js/index',
8 |
9 | output: {
10 | path: path.resolve('./assets/bundles'),
11 | filename: '[name].js',
12 | },
13 |
14 | plugins: [
15 | new webpack.ProvidePlugin({
16 | $: 'jquery',
17 | jQuery: 'jquery',
18 | 'window.jQuery': 'jquery'
19 | }),
20 | ],
21 |
22 | module: {
23 | loaders: [
24 | {
25 | test: /\.jsx?$/,
26 | exclude: /node_modules/,
27 | loader: 'babel-loader',
28 | query: {
29 | presets: ['react', 'es2016']
30 | }
31 | },
32 | ]
33 | },
34 |
35 | resolve: {
36 | modulesDirectories: ['node_modules'],
37 | extensions: ['', '.js', '.jsx']
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/django_react_auth/urls.py:
--------------------------------------------------------------------------------
1 | """django_react_auth URL Configuration
2 |
3 | The `urlpatterns` list routes URLs to views. For more information please see:
4 | https://docs.djangoproject.com/en/1.9/topics/http/urls/
5 | Examples:
6 | Function views
7 | 1. Add an import: from my_app import views
8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
9 | Class-based views
10 | 1. Add an import: from other_app.views import Home
11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
12 | Including another URLconf
13 | 1. Import the include() function: from django.conf.urls import url, include
14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
15 | """
16 | from django.conf.urls import url, include
17 | from django.views.generic import TemplateView
18 | from django.views.decorators.csrf import csrf_exempt
19 |
20 | urlpatterns = [
21 | url(r'^app/', csrf_exempt(TemplateView.as_view(template_name='index.html'))),
22 | url(r'^api/', include('api.urls'))
23 | ]
24 |
25 |
--------------------------------------------------------------------------------
/assets/js/login.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react')
2 | var auth = require('./auth')
3 |
4 | module.exports = React.createClass({
5 | contextTypes: {
6 | router: React.PropTypes.object.isRequired
7 | },
8 |
9 | getInitialState: function() {
10 | return {
11 | login_error: false
12 | }
13 | },
14 |
15 | handleSubmit: function(e) {
16 | e.preventDefault()
17 |
18 | var username = this.refs.username.value
19 | var pass = this.refs.pass.value
20 |
21 | auth.login(username, pass, (loggedIn) => {
22 | if (loggedIn) {
23 | this.context.router.replace('/app/')
24 | } else {
25 | this.setState({login_error:true})
26 | }
27 | })
28 | },
29 |
30 | render: function() {
31 | return (
32 |
37 | )
38 | }
39 | })
40 |
--------------------------------------------------------------------------------
/assets/js/app.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react')
2 | var auth = require('./auth')
3 |
4 | module.exports = React.createClass({
5 | getInitialState: function() {
6 | return {'user':[]}
7 | },
8 |
9 | componentDidMount: function() {
10 | this.loadUserData()
11 | },
12 |
13 | contextTypes: {
14 | router: React.PropTypes.object.isRequired
15 | },
16 |
17 | logoutHandler: function() {
18 | auth.logout()
19 | this.context.router.replace('/app/login/')
20 | },
21 |
22 | loadUserData: function() {
23 | $.ajax({
24 | method: 'GET',
25 | url: '/api/users/i/',
26 | datatype: 'json',
27 | headers: {
28 | 'Authorization': 'Token ' + localStorage.token
29 | },
30 | success: function(res) {
31 | this.setState({user: res})
32 | }.bind(this)
33 | })
34 | },
35 |
36 | render: function() {
37 | return (
38 |
39 |
You are now logged in, {this.state.user.username}
40 |
41 |
42 | )
43 | }
44 | })
45 |
--------------------------------------------------------------------------------
/assets/js/auth.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | login: function(username, pass, cb) {
3 | if (localStorage.token) {
4 | if (cb) cb(true)
5 | return
6 | }
7 | this.getToken(username, pass, (res) => {
8 | if (res.authenticated) {
9 | localStorage.token = res.token
10 | if (cb) cb(true)
11 | } else {
12 | if (cb) cb(false)
13 | }
14 | })
15 | },
16 |
17 | logout: function() {
18 | delete localStorage.token
19 | },
20 |
21 | loggedIn: function() {
22 | return !!localStorage.token
23 | },
24 |
25 | getToken: function(username, pass, cb) {
26 | $.ajax({
27 | type: 'POST',
28 | url: '/api/obtain-auth-token/',
29 | data: {
30 | username: username,
31 | password: pass
32 | },
33 | success: function(res){
34 | cb({
35 | authenticated: true,
36 | token: res.token
37 | })
38 | },
39 | error: (xhr, status, err) => {
40 | cb({
41 | authenticated: false
42 | })
43 | }
44 | })
45 | },
46 | }
47 |
--------------------------------------------------------------------------------
/django_react_auth/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for django_react_auth project.
3 |
4 | Generated by 'django-admin startproject' using Django 1.9.4.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/1.9/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/1.9/ref/settings/
11 | """
12 |
13 | import os
14 |
15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17 |
18 |
19 | # Quick-start development settings - unsuitable for production
20 | # See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
21 |
22 | # SECURITY WARNING: keep the secret key used in production secret!
23 | SECRET_KEY = '@g$7kxw@0uq0k*u^cyga^y6)@#ro@2y6dve+y#=2r3-whw@p*7'
24 |
25 | # SECURITY WARNING: don't run with debug turned on in production!
26 | DEBUG = True
27 |
28 | ALLOWED_HOSTS = []
29 |
30 |
31 | # Application definition
32 |
33 | INSTALLED_APPS = [
34 | 'django.contrib.admin',
35 | 'django.contrib.auth',
36 | 'django.contrib.contenttypes',
37 | 'django.contrib.sessions',
38 | 'django.contrib.messages',
39 | 'django.contrib.staticfiles',
40 | #third-party apps
41 | 'rest_framework',
42 | 'rest_framework.authtoken',
43 | #internal apps
44 | 'api'
45 | ]
46 |
47 | MIDDLEWARE_CLASSES = [
48 | 'django.middleware.security.SecurityMiddleware',
49 | 'django.contrib.sessions.middleware.SessionMiddleware',
50 | 'django.middleware.common.CommonMiddleware',
51 | 'django.middleware.csrf.CsrfViewMiddleware',
52 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
53 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
54 | 'django.contrib.messages.middleware.MessageMiddleware',
55 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
56 | ]
57 |
58 | ROOT_URLCONF = 'django_react_auth.urls'
59 |
60 | TEMPLATES = [
61 | {
62 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
63 | 'DIRS': [os.path.join(BASE_DIR, 'templates')],
64 | 'APP_DIRS': True,
65 | 'OPTIONS': {
66 | 'context_processors': [
67 | 'django.template.context_processors.debug',
68 | 'django.template.context_processors.request',
69 | 'django.contrib.auth.context_processors.auth',
70 | 'django.contrib.messages.context_processors.messages',
71 | ],
72 | },
73 | },
74 | ]
75 |
76 | WSGI_APPLICATION = 'django_react_auth.wsgi.application'
77 |
78 |
79 | # Database
80 | # https://docs.djangoproject.com/en/1.9/ref/settings/#databases
81 |
82 | DATABASES = {
83 | 'default': {
84 | 'ENGINE': 'django.db.backends.sqlite3',
85 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
86 | }
87 | }
88 |
89 |
90 | # Password validation
91 | # https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
92 |
93 | AUTH_PASSWORD_VALIDATORS = [
94 | {
95 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
96 | },
97 | {
98 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
99 | },
100 | {
101 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
102 | },
103 | {
104 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
105 | },
106 | ]
107 |
108 |
109 | # Internationalization
110 | # https://docs.djangoproject.com/en/1.9/topics/i18n/
111 |
112 | LANGUAGE_CODE = 'en-us'
113 |
114 | TIME_ZONE = 'UTC'
115 |
116 | USE_I18N = True
117 |
118 | USE_L10N = True
119 |
120 | USE_TZ = True
121 |
122 | REST_FRAMEWORK = {
123 | 'DEFAULT_AUTHENTICATION_CLASSES': (
124 | 'rest_framework.authentication.TokenAuthentication',
125 | )
126 | }
127 |
128 |
129 | # Static files (CSS, JavaScript, Images)
130 | # https://docs.djangoproject.com/en/1.9/howto/static-files/
131 |
132 | STATIC_URL = '/static/'
133 | STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
134 |
135 | STATICFILES_DIRS = (
136 | os.path.join(BASE_DIR, 'assets'),
137 | )
138 |
139 |
--------------------------------------------------------------------------------