├── 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 |
33 | 34 | 35 | 36 |
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 | --------------------------------------------------------------------------------