Tumblelog by Soup.io
Newer posts are loading.
You are at the newest post.
Click here to check if anything new just came in.

April 22 2018

Authenticating via JWT using Django, Axios, and Vue

Getting Django Rest Framework, JWT, Axios, and Vue.js to play nice isn't easy. Here's my quick-and-dirty cheatsheet that I wrote while glueing the pieces together.

Note: My architecture doesn't use django-webpack-loader. Instead, I'm running Django and Vue.js as two separate projects. I do this because I much prefer generating new projects with vue create over configuring webpack.

The Back-End

First, install some Django parts using the installer of your choice:


pip install Django
pip install djangorestframework
pip install django-cors-headers
pip install djangorestframework-jwt

Then, configure Django in settings.py:


INSTALLED_APPS = (
    ...
    'rest_framework',
    'rest_framework.authtoken',  
    'rest_framework.authtoken',
    'corsheaders',
)


MIDDLEWARE_CLASSES = (
  ...
  'corsheaders.middleware.CorsMiddleware',
)

CORS_ORIGIN_ALLOW_ALL = False
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = (
    # TODO - set this properly for production
    'http://127.0.0.1:8080',
    'http://127.0.0.1:8000',
)


REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        # By default we set everything to admin,
        #   then open endpoints on a case-by-case basis
        'rest_framework.permissions.IsAdminUser',
    ),
    'TEST_REQUEST_RENDERER_CLASSES': (
        'rest_framework.renderers.MultiPartRenderer',
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.TemplateHTMLRenderer'
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ),
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 20,
}

from datetime import timedelta

JWT_AUTH = {
    'JWT_ALLOW_REFRESH': True,
    'JWT_EXPIRATION_DELTA': timedelta(hours=1),
    'JWT_REFRESH_EXPIRATION_DELTA': timedelta(days=7),
}

Once that's done, it's time to do modify the urls.py:


from django.conf.urls import include, url
...

urlpatterns = [
    ...
    # JWT auth
    url(r'^api/v1/auth/obtain_token/', obtain_jwt_token),
    url(r'^api/v1/auth/refresh_token/', refresh_jwt_token),    
    # The rest of the endpoints
    url(r'^api/v1/', include('project.api', namespace='apiv1')),
    ...
]

Run the tests and fix them as they fail.

The Front-End

First off, add this to your Vuex store:


import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'

Vue.use(Vuex)

// Make Axios play nice with Django CSRF
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = 'X-CSRFToken'

export default new Vuex.Store({
  state: {
    authUser: {},
    isAuthenticated: false,
    jwt: localStorage.getItem('token'),
    endpoints: {
      // TODO: Remove hardcoding of dev endpoints
      obtainJWT: 'http://127.0.0.1:8000/api/v1/auth/obtain_token/',
      refreshJWT: 'http://127.0.0.1:8000/api/v1/auth/refresh_token/',
      baseUrl: 'http://127.0.0.1:8000/api/v1/'
    }
  },

  mutations: {
    setAuthUser(state, {
      authUser,
      isAuthenticated
    }) {
      Vue.set(state, 'authUser', authUser)
      Vue.set(state, 'isAuthenticated', isAuthenticated)
    },
    updateToken(state, newToken) {
      // TODO: For security purposes, take localStorage out of the project.
      localStorage.setItem('token', newToken);
      state.jwt = newToken;
    },
    removeToken(state) {
      // TODO: For security purposes, take localStorage out of the project.
      localStorage.removeItem('token');
      state.jwt = null;
    }
  }
})

Then, in a Vue component called something like components/Login.vue:


<template lang="html">
  <form class="login form">
    <div class="field">
      <label for="id_username">Username</label>
      <input
        v-model="username"
        type="text"
        placeholder="Username"
        autofocus="autofocus"
        maxlength="150"
        id="id_username">
    </div>
    <div class="field">
      <label for="id_password">Password</label>
      <input
        v-model="password"
        type="password"
        placeholder="Password"
        id="id_password">
    </div>
    <button
      @click.prevent="authenticate"
      class="button primary"
      type="submit">
      Log In
    </button>
  </form>
</template>

<script>
import axios from 'axios'

export default {
  data () {
    return {
      username: '',
      password: ''
    }
  },
  methods: {
    authenticate () {
      const payload = {
        username: this.username,
        password: this.password
      }
      axios.post(this.$store.state.endpoints.obtainJWT, payload)
        .then((response) => {
          this.$store.commit('updateToken', response.data.token)
          // get and set auth user
          const base = {
            baseURL: this.$store.state.endpoints.baseUrl,
            headers: {
            // Set your Authorization to 'JWT', not Bearer!!!
              Authorization: `JWT ${this.$store.state.jwt}`,
              'Content-Type': 'application/json'
            },
            xhrFields: {
                withCredentials: true
            }
          }
          // Even though the authentication returned a user object that can be
          // decoded, we fetch it again. This way we aren't super dependant on
          // JWT and can plug in something else.
          const axiosInstance = axios.create(base)
          axiosInstance({
            url: "/user/",
            method: "get",
            params: {}
          })
            .then((response) => {
              this.$store.commit("setAuthUser",
                {authUser: response.data, isAuthenticated: true}
              )
              this.$router.push({name: 'Home'})
            })

        })
        .catch((error) => {
          console.log(error);
          console.debug(error);
          console.dir(error);
        })
    }
  }
}
</script>

<style lang="css">
</style>

Summary

There you have it, my quick-and-dirty notes on getting Django REST Framework, JWT, Axios, and Vue.js to play nice together. Be aware there are a two significant problems:

  1. I'm not happy about using local storage, especially with JWT. In fact, my good friend Randall Degges has written about the problems of JWT.
  2. I don't cover logging out. ;)

Credits:

April 21 2018

April 20 2018

The final INTERNAL_IPS fix for development hosts

The final INTERNAL_IPS fix for development hosts

Django’s INTERNAL_IPS setting is an ongoing source of frustration and confusion (not only, but also) for users of django-debug-toolbar, especially when using non-local addresses. This is very useful for testing a website using mobile devices if you do not have a very fast internet connection where it does not matter whether you connect to a host through the local network or via the internet, for example using localtunnel.

For some time we had a utility function which automatically added all detected network interface IPs to INTERNAL_IPS. However, this does not work when using virtualization software such as Docker or Vagrant with port forwarding, because the VM’s (or container’s) IP isn’t what you want – you want the host IP.

Once I took a step back I saw a different, but much simpler solution. INTERNAL_IPS can be replaced with an object which simply answers True to all __contains__-type questions:

if DEBUG:
    # `debug` is only True in templates if the vistor IP is in INTERNAL_IPS.
    INTERNAL_IPS = type(str('c'), (), {'__contains__': lambda *a: True})()

April 19 2018

How to Apply Image Filters in OpenCV with Python

Add your own image filters on ...

Goodbye manage.py

Every Django project starts with a manage.py file in its root. It's a convenience script that allows you to run administrative tasks like Django's included django-admin.

In our last post, we discussed the merits of including a setup.py file in your project to make it installable as a true Python package. We'll build on that and show you how to properly setup a console script in the package.

Hello Entry Points

The setup function provided by setuptools accepts an entry_points keyword argument that is used to dynamically create scripts from a provided Python function on install. If we crack open the existing manage.py and strip it down to its essentials, we can see it isn't really doing much:

import os
import sys

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)

It sets a default environment variable and then calls a function passing in the provided command line arguments.

This can easily be turned into an entry point by simply making it a function inside our project. I typically add it to myproject/__init__.py like so:

import os
import sys

def manage():
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
    from django.core.management import execute_from_command_line
    execute_from_command_line(sys.argv)

Now we adjust our setup.py to add the entry point:

setup(
    ...
    entry_points={'console_scripts': [
        'manage.py = myproject:manage',
    ]}
)

On install, a manage.py script will be created and added to the virtualenv's path.

It's also worth noting that the script's name is purely vestigal at this point. If you are targeting non-Django developers with your project, it might make more sense to rename it to something more logical.

Benefits

There are a few things I like about this approach.

  1. You can run manage.py from anywhere. No need to activate a virtualenv or be in the right directory. Simply run /path/to/virtualenv/bin/manage.py ... and it just works. If your virtualenv is activated, the path will already be set and manage.py will just work without the path.
  2. This is purely cosmetic, but I don't like the proliferation of files in the project root (, .dockerignore, Procfile, webpack.config.js, etc.). One less file makes me happy.

One potential downside to this approach is that manage.py is often a signal to developers, "this is a Django project". By removing it, you're more reliant on the docs to point developers in the right direction. If you prefer to keep your manage.py file, you can still benefit from this approach by wrapping the code inside a function and registering that as an entrypoint.

Have any other Django install tricks you like to use on your projects? I'd love to hear them. Please leave a note in the comments.

Django - Referencing the User Model

There are 3 different ways to refer to the User model in Django. This post explains the differences and which approach to use.

April 18 2018

Offline messages for Django

Offline messages for Django

django.contrib.messages is awesome. Its API surface is small, it’s fast and it does what it does really well.

However, it sometimes happens that you would want to send messages to users outside the request-response cycle, for example from a background process or even from a post_save signal handler.

Adding a full-blown notifications-and-messages-system is of course possible. Sending a one-off message to the user would be sufficient though, but there is no way to do this with Django’s messages framework.

There are a few packages around solving this particular problem. Almost all of them solve this by saving undelivered messages in the database. My solution, django-user-messages follows the same approach, but contrary to many others it does not reimplement the message storage nor replace any other functionality of django.contrib.messages. It only adds a few additional utilities for adding messages (e.g. user_messages.api.info(...) instead of messages.info(...)) and a context processor which concatenates Django’s messages with those provided by django-user-messages.

Instead of passing the request to messages.info(...) you would pass a user instance, or even only a user ID to api.info(...).

Easy enough, and works well. Despite the 0.5 version number the package has been stable and essentially unchanged since July 2017 (except for the addition of Python 2 support). So I wont say to get it while it’s hot, because it’s not – instead, it is boring, stable and reliable, the way I like my low maintenance software.

April 17 2018

3rd Party Haar Cascades in OpenCV

Building on [face recognition ...

April 16 2018

April 15 2018

April 14 2018

Demystifying encodings — part 3

Do you ever have trouble with encodings? I used to in the past, but now I rarely do, because I do a simple thing. While knowing the things I explained in part 1 and part 2 of this series is necessary, otherwise I wouldn’t be able to fix problems, such problems rarely arise, because the first thing I do when I setup a GNU/Linux machine is set the system locale to UTF-8.

The “locale” is the regional settings, among which the character encoding used. The procedure to set it up is this:

  1. Open the file /etc/locale.gen in an editor and make sure the line that begins with “en_US.UTF-8” is uncommented.
  2. Enter the command locale-gen; this will (re)generate the locales.
  3. Open the file /etc/default/locale in an editor, and make sure it contains the line LANG=en_US.UTF-8. Changes in this file require logout and login to take effect.

Let me now explain what all this is about. The locale consists of a language, a country, and a character encoding; “en_US.UTF-8” means English, United States, UTF-8. This tells programs to show messages in American English; to format items such as dates in the way it’s done in the United States; and to use encoding UTF-8.

Different users can be using different locales. If you have a desktop computer used by you and your spouse, one could be using English and the other French. Each user does this by setting the LANG environment variable to the desired locale; if not, the default system locale is used for that user. For servers this feature is less important. While your Django application may display the user interface in different languages (and format dates and numbers in different ways), this is done by Django itself using Django’s internationalization and localization machinery and has nothing to do with what we are discussing here, which affects mostly the programs you type in the command line, such as ls. Because for servers the feature of users specifying their preferred locale isn’t so important, we usually merely use the default system locale, which is specified in the file /etc/default/locale. Since you can understand English, “en_US.UTF-8” is fine. If you prefer to use another country, such as “en_UK.UTF-8”, it’s also fine, but it’s no big deal, as I will explain later on.

Although the system can support a large number of locales, many of these are turned off in order to save a little disk space. You turn them on by adding or uncommenting them in file /etc/locale.gen. When you execute the program locale-gen, it reads /etc/locale.gen and determines which locales are activated, and it compiles these locales from their source files, which are relatively small, to some binary files that are those actually used by the various programs. We say that the locales are “generated”. If you activate all locales the binary files will be a little bit over 100 M, so the saving is not that big (it was important 15 years ago); however they will take quite some time to generate. Usually we only activate a few.

To check that everything is right, do this:

  1. Enter the command locale; everything (except, possibly, LANGUAGE and LC_ALL) should have the value en_US.UTF-8.Enter the command
  2. perl -e ''; it should do nothing and give no message.

The locale command merely lists the active locale parameters. LC_CTYPE, LC_NUMERIC etc. are called “locale categories”, and usually they are all set to the same value. In some edge cases they might be set to different values; for example, on my laptop I use “en_US.UTF-8”, but especially for LC_TIME I use “en_DK.UTF-8”, which causes Thunderbird to display dates in ISO 8601. This is not our concern here and it rarely is on a server. So we don’t set any of these variables, and they all get their value from LANG, which is set by /etc/default/locale.

However, sometimes you might make an error; you might specify a locale in /etc/default/locale, but you might forget to generate it. In that case, the locale command will indicate that the locale is active, but it will not show that anything is wrong. This is the reason I run perl -e ''. Perl is a programming language, like Python. The command perl -e '', does nothing; it tells Perl to execute an empty program; same thing as python -c ''. However, if there is anything wrong with the locale, Perl throws a big warning message; so perl -e '' is my favourite way of verifying that my locale works. Try, for example, LANG=el_GR.UTF-8 perl -e '' to see the warning message. So locale shows you which is the active locale, and perl -e '', if silent, indicates that the active locale has been generated and is valid.

I told you that the country doesn’t matter much for servers. Neither does the language. What matters is the encoding. You want to be able to manipulate all characters of all languages. Even if all your customers are English speaking, there may eventually be some remark about a Chinese character in a description field. Even if you are certain there won’t, it doesn’t make any sense to constrain yourself to an encoding that can represent only a subset of characters when it’s equally easy to use UTF-8. So you need to make sure you use UTF-8.

PostgreSQL will use the default locale for its databases (unless told to do otherwise). Your best bet is to do all the above before apt install postgresql.

The programs you run at the command line will be producing output in your chosen encoding. Your terminal reads the bytes produced by these programs and must be able to decode them properly, so it must know how they are encoded. In other words, you must set your terminal to UTF-8 as well. Most terminals, including PuTTY and gnome-terminal, are by default set to UTF-8, but you can change that in their preferences.

This post is largely taken from the first chapter of my book.

Was this post useful?

The post Demystifying encodings — part 3 appeared first on Django deployment.

April 13 2018

April 12 2018

OpenCV & Python: Face Recognition and Identification

I'm really starting to enjoy w...

Save an Auto Generated PDF File to Django model

To save a auto-generated PDF f...

April 11 2018

For hire

Since a slightly-wider circle of people know this now, it’s time to just go public with the news: my last day at now-former employer was toward the end of January. At the time I told a few friends, but wasn’t in a huge rush to find something else immediately; I’d been getting ready to depart for a little while, and there were some things (a bit of travel, catching up on some open-source projects, and ...

Read full entry

April 09 2018

April 06 2018

Dynamic generate urlpatterns for django 2

Create dynamic urlpatterns for classed based views

February 17 2018

Django Generate Unique Slug

Generate unique slug refference by another field.

January 31 2018

Online Dating

Quackquack is one of the best online dating site to chat, date and browse personals online. Register now and find perfect life partner from thousands of singles.

Older posts are this way If this message doesn't go away, click anywhere on the page to continue loading posts.
Could not load more posts
Maybe Soup is currently being updated? I'll try again automatically in a few seconds...
Just a second, loading more posts...
You've reached the end.

Don't be the product, buy the product!

Schweinderl