Django to Phoenix - Part 4: Sessions
Tags: django, phoenix
Published: 2017-11-12 16:24:22

---
title: "Django to Phoenix - Part 4: Sessions"
published: true
description: 
tags: 
  - Django
  - Phoenix
---


## Background

I have a website built with [Django](https://www.djangoproject.com/) and I would like to start writing parts of it in [Phoenix](http://phoenixframework.org/).

- [Part 0: Background](https://dev.to/michaeljones/django-to-phoenix-part-0-6ij)
- [Part 1: Docker for Dev](https://dev.to/michaeljones/django-to-phoenix-part-1-56h)
- [Part 2: Nginx](https://dev.to/michaeljones/django-to-phoenix---part-2-nginx-9kg)
- [Part 3: Initial Phoenix Setup](https://dev.to/michaeljones/django-to-phoenix---part-3-initial-phoenix-setup-4ma)


## Disclaimer

I am a beginner at Phoenix. I would like help & advice if I am doing something wrong.


## Starting Point

In the last post we set up Phoenix inside our [Docker](https://www.docker.com/) container. We have [nginx](https://www.nginx.com/resources/wiki/) splitting traffic between Phoenix & Django. All requests go to Django except for the temporary `/elixir` path which we direct to Phoenix for experimenting.

In this post, we're going to look at accessing the Django sessions information so that our Phoenix app can know if the user is logged in or not. We're going to have to:

- Check the session cookie
- Check the sessions table in the database
- Add some information to the request data to indicate if there is a current user session or not.
- Displaying some indication of the session on the page.


## Checking the Session Cookie

The [official documentation](https://docs.djangoproject.com/en/1.11/topics/http/sessions) tells us that Django uses cookies for tracking the session IDs. If we log in to our Django app in Chrome, open up the [inspector](https://developers.google.com/web/tools/chrome-devtools/), go to the Application tab and then click on the `localhost` entry under [Cookies](https://developers.google.com/web/tools/chrome-devtools/manage-data/cookies), then we can see a cookie called `sessionid` which contains a string.

I'm not cookie expert but my understanding is that any available cookies for the domain are automatically sent along with each standard web request. So when we navigate to `/elixir` the browser will be sending the same cookies over.

Phoenix is built on top of a layer called [Plug](https://hexdocs.pm/plug/readme.html). Looking at the [documentation for Plug](https://hexdocs.pm/plug/Plug.Conn.html#module-fetchable-fields) we can see that the cookies are available as an Elixir [Map](https://hexdocs.pm/elixir/Map.html) on the `conn` data. This is the standard connection data that is fed to functions that are dealing with requests.

In order to access that cookie we're going to set up a new Phoenix controller called `TimetableWeb.Session`. To do this we create a file called `session.ex` in the directory `lib/timetable_web/controllers/` in our Phoenix project. It will have the following contents:

```elixir
defmodule TimetableWeb.Session do
  import Plug.Conn

  def init(_) do
  end

  def call(conn, _) do
    case conn.cookies["sessionid"] do
      nil ->
        IO.puts "No sessionid found"
      session_id ->
        IO.puts "Session ID: #{session_id}"
    end
 
    # Return conn
    conn
  end
end
```

This is standard Plug format. For the moment, we don't need to do anything in the `init` function but it still required. The `call` function requires two arguments in order to receive the data from `init` but we don't need it so we use a simple `_` as the argument name.

As for the rest of the Plug, we look into the `conn.cookies` Map for the `sessionid` entry and [pattern match](http://elixir-lang.github.io/getting-started/pattern-matching.html) on the result. If we get `nil` then we know we don't have an active session at the moment. If we get something other than `nil` then we know we must have the session ID. For the moment, we print an appropriate message and return the `conn` at the end of the function. In Elixir the result of the last expression is returned from the function. There is no need for an explicit `return` keyword in that situation.

Now we need to add our plug into the Phoenix request pipeline so that it can process incoming requests. To do this we edit our `lib/timetable_web/router.ex` file with the following change:

```diff
   pipeline :browser do
     plug :accepts, ["html"]
     plug :fetch_session
     plug :fetch_flash
     plug :protect_from_forgery
     plug :put_secure_browser_headers
+    plug TimetableWeb.Session, repo: Timetable.Repo
   end
 
   pipeline :api do
     plug :accepts, ["json"]
   end
```

Here we are adding our plug to the end of the browser pipeline. We're not using the `api` pipeline at the moment so we ignore that.

Now, if we go to our Django app, log in and visit `/elixir` then we see:

```console
[info] GET /elixir
Session ID: boj26dqs1v0okevigm2vnsu11gephx21
```

In our console output from the Phoenix app inside our Docker container.

If we now go to our Django app, log out and visit `/elixir`, then we see: 

```console
[info] GET /elixir
No sessionid found
```

So we seem to be successfully getting the session cookie.


## Checking the Sessions Database Table

Django doesn't store meaningful data in the session cookie. It is just an ID to access the data from the sessions table in the database. The [documentation](https://docs.djangoproject.com/en/1.11/topics/http/sessions/#technical-details) tells us that the session data is stored in a table called `django_session`. If we run `python manage.py dbshell` and in the [Postgresql](https://www.postgresql.org/) prompt run: `\d django_session` then we get a description of the table:

```console
            Table "public.django_session"
    Column    |           Type           | Modifiers 
--------------+--------------------------+-----------
 session_key  | character varying(40)    | not null
 session_data | text                     | not null
 expire_date  | timestamp with time zone | not null
Indexes:
    "django_session_pkey" PRIMARY KEY, btree (session_key)
    "django_session_expire_date" btree (expire_date)
    "django_session_session_key_like" btree (session_key varchar_pattern_ops)
```

We can see that there are 3 columns. The `session_key` most likely maps to our `session_id`. The `session_data` most likely includes the useful information about the session. Finally, there is an `expire_date` to indicate when the session should be considered 'stale' and no longer honoured. We can also see that the `session_key` field is the primary key for this table.

We want to get to a position in which our Phoenix app can check this database table to retrieve the session information. Phoenix uses a layer called [Ecto](https://hexdocs.pm/ecto/Ecto.html) to model database tables and talk to databases. We are going to use a [mix](https://hexdocs.pm/mix/Mix.html) task to help generate Ecto schemas. The command we're going to run is:

```console
mix phx.gen.schema Session sessions session_key:string session_data:string expire_date:date
```

This generates an Ecto schema that is almost correct for what we need. The problem is that it assumes that we're going to want to have an extra `id` column to use as the primary key. This makes sense for a lot of tables but not our `django_session` table where the primary key is the `session_key` column. So we have to adjust the final schema to inform Ecto of our preferred primary key. Here is what it is going to look like:

```elixir
defmodule Timetable.Session do
  use Ecto.Schema
  import Ecto.Changeset
  alias Timetable.Session

  @primary_key {:session_key, :string, autogenerate: false}

  schema "django_session" do
    field :session_data, :string
    field :expire_date, :utc_datetime
  end

  @doc false
  def changeset(%Session{} = session, attrs) do
    session
    |> cast(attrs, [:session_key, :session_data, :expire_date])
    |> validate_required([:session_key, :session_data, :expire_date])
  end
end
```

To check if this works, we can try it in the Elixir interactive shell: [iex](https://hexdocs.pm/iex/IEx.html). If we run `iex -S mix` from Phoenix project root directory, then we can do:

```iex
iex(1)> Timetable.Session |> Timetable.Repo.get_by(session_key: "boj26dqs1v0okevigm2vnsu11gephx21")                                                       
[debug] QUERY OK source="django_session" db=131.4ms                          
SELECT d0."session_key", d0."session_data", d0."expire_date" FROM "django_session" AS d0 WHERE (d0."session_key" = $1) ["boj26dqs1v0okevigm2vnsu11gephx21"]                                     
%Timetable.Session{__meta__: #Ecto.Schema.Metadata<:loaded, "django_session">,                                                                            
 expire_date: #DateTime<2017-11-21 08:31:06.774119Z>,                        
 session_data: "ODNjNWFkMDJh....cl9pZCI6IjEifQ==",  
 session_key: "boj26dqs1v0okevigm2vnsu11gephx21"
``` 

We can see that we received a record with the session data that we're interested in.

If we want to check the `expire_date` as well we can do this using the Ecto query syntax:

```iex
iex(1)> import Ecto.Query, only: [from: 2]
Ecto.Query
iex(2)> now = DateTime.utc_now()
#DateTime<2017-11-12 10:31:16.153448Z>
iex(3)> (from s in Timetable.Session, where: s.session_key == "boj26dqs1v0okevigm2vnsu11gephx21" and s.expire_date >= ^now) |> Timetable.Repo.one
[debug] QUERY OK source="django_session" db=24.0ms
SELECT d0."session_key", d0."session_data", d0."expire_date" FROM "django_session" AS d0 WHERE ((d0."session_key" = 'boj26dqs1v0okevigm2vnsu11gephx21') AND (d0."expire_date" >= $1)) [{{2017, 11, 12}, {10, 31, 16, 153448}}]
%Timetable.Session{__meta__: #Ecto.Schema.Metadata<:loaded, "django_session">,
  expire_date: #DateTime<2017-11-21 08:31:06.774119Z>,
  session_data: "ODNjNWFkMDJh....cl9pZCI6IjEifQ==",
  session_key: "boj26dqs1v0okevigm2vnsu11gephx21"}
```

And to check that it returns nothing when the session has expired we can do:

```iex
iex(1)> import Ecto.Query, only: [from: 2]
Ecto.Query
iex(2)> {:ok, later, 0} = DateTime.from_iso8601("2017-11-22T00:00:00Z")                                                                            
{:ok, #DateTime<2017-11-22 00:00:00Z>, 0}
iex(3)> (from s in Timetable.Session, where: s.session_key == "boj26dqs1v0okevigm2vnsu11gephx21" and s.expire_date >= ^later) |> Timetable.Repo.one
[debug] QUERY OK source="django_session" db=2.9ms
SELECT d0."session_key", d0."session_data", d0."expire_date" FROM "django_session" AS d0 WHERE ((d0."session_key" = 'boj26dqs1v0okevigm2vnsu11gephx21') AND (d0."expire_date" >= $1)) [{{2017, 11, 22}, {0, 0, 0, 0}}]
nil
```

We get back `nil` when providing a date after the session `expire_date`.


## Adding Session Information to the Request

Now that we have a way to extract information from the session table, we can update our Session plug to check for a valid session. We're going to update it to look like this:

```elixir
defmodule TimetableWeb.Session do
  import Plug.Conn
  import Ecto.Query, only: [from: 2]

  def init(opts) do
    Keyword.fetch!(opts, :repo)
  end

  def call(conn, repo) do
    now = DateTime.utc_now()
    case conn.cookies["sessionid"] do
      nil ->
        assign(conn, :logged_in, false)
      session_key ->
        case repo.one(from s in Timetable.Session, where: s.session_key == ^session_key and s.expire_date >= ^now) do
          nil ->
            assign(conn, :logged_in, false)
          _ ->
            assign(conn, :logged_in, true)
        end
    end
  end
end

```

We have done two main adjustments:
1. The `init` function has been updated to look for and return the `:repo` reference from the `opts`. The `call` function has been updated to receive the `repo` as the second argument. 
2. If the `call` function has found a session key in the cookie, we check the sessions table for that session. If the session is valid, then we return the `conn` data with a new `logged_in` field set to true. In both of the other cases where no valid cookie or session is found, we return the `conn` with a `logged_in` field set to false.

This isn't very advanced yet. A normal system would want to access the `session_data` from the session table as well but we'll leave that for the next post.  

## Displaying Session Information on the Page

For the final step, we want to display whether or not the user is logged in. We're going to keep it very basic for now and update the template for the Phoenix landing page which is `lib/timetable_web/templates/page/index.html.eex`. We're going to make the follow change: 

```diff
 <div class="row marketing">
+
+  <div>
+    <%= if @logged_in do %>
+      Logged In!
+    <% else %>
+      Not Logged In
+    <% end %>
+  </div>
+
   <div class="col-lg-6">
     <h4>Resources</h4>
```
This uses [EEx](https://hexdocs.pm/eex/EEx.html) - Embedded Elixir - to display different content depending on the value of `logged_in`. As a field on `conn`, the `logged_in` value is accessible in our templates.  

Now when we log in to our Django app and navigate to our `/elixir` page, we're going to see `Logged In!` displayed to us on the page. And if we log out of our Django app and come back to this page we see `Not Logged In` instead.

## Conclusion

We have our Phoenix app reading the session information from the Django sessions table. We are able to update the web page in a basic manner to reflect the logged-in status of the current user. 

In the next post, we'll extend this approach to also check the user table for the user name.