Elixir and Phoenix Decoded: Configuring Guardian and GuardianDb
Update: I’ve recently changed some of the original config with generating the secret keys. Unfortunately, I found my original method was incorrect as I was referencing an article that was written for Guardian v0.8. I’ve also added configuration for GuardianDb’s ExpiredSweeper worker.
If you’re rolling out your own user authentication in an Elixir & Phoenix application, you’ll most likely want to use Guardian and GuardianDb to manage your JSON Web Tokens (JWTs). Utilizing both libraries allows you to be able to revoke expired tokens from a client from the server side of things instead of relying on the client “forgetting” about the token. The libraries are fairly easy to use and mostly rely on using the various plugs they ship with.
The most intimidating part of using Guardian and GuardianDb is the initial setup and configuration required. Originally, when trying to find resources to help with the process, I felt that they fell somewhat flat in terms of the crucial details needed to get it working. I’ve done my best to lay out the specifics so let’s dive right in!
Adding the Necessary Dependencies
First, lets add the dependencies needed to set up Guardian and GuardianDb. We’ll need to add JOSE (JSON Object Signing and Encryption), Poison (a JSON library needed to use JOSE), and of course Guardian and GuardianDb! In your “mix.exs” file add the following to your project dependencies:
defp deps do
[...,
{:jose, "~>1.8"},
{:poison, "~>2.2"},
{:guardian, "~> 0.14"},
{:guardian_db, "~>0.8.0"}]
endNote: You may need to add the Guardian and GuardianDb project dependencies after completing the rest of this tutorial instead of at this point. Unfortunately, it seems Guardian requires a completed config before it will retrieve its dependencies so you may not be able to continue otherwise.
Now add JOSE to your application dependencies:
def application do
[...,
applications: [..., :jose,]]
endGenerating the Guardian Secret Key using JOSE
Next, let’s use JOSE to generate a secret key for Guardian. Open your terminal and inside of your project directory bring up the interactive Elixir shell:
iex -S mixOnce inside the shell, run the following Elixir code:
iex(1)> secret_key = JOSE.JWS.generate_key(%{"alg" => "ES512"}) |> JOSE.JWK.to_map |> elem(1)This will generate a secret key to use for Guardian (using the default HS512 algorithm). We’ll simply copy the map that results from this command directly into our dev config since it is local. For production, you should set the results of this command in an environment variable.
Note: You should generate a new key for each environment you use. Do not use the same secret key you used for development.
Setting up the Guardian Serializer
Guardian requires defining your own serializer to encode and decode your user’s data within the JWT. Let’s define a basic serializer. In your project, create a folder in the “web” directory called “serializers”. In this example, I’ve named my serializer file “guardian_serializer.ex”:
defmodule YourAppNameHere.GuardianSerializer do
@behaviour Guardian.Serializer alias YourAppNameHere.Repo
alias YourAppNameHere.User def for_token(user = %User{}), do: { :ok, "User:#{user.id}" }
def for_token(_), do: { :error, "Unknown resource type" } def from_token("User:" <> id), do: { :ok, Repo.get(User, id) }
def from_token(_), do: { :error, "Unknown resource type" }
end
You can see in this serializer that we encode the user’s id into the token since it is a unique identifier that will vary for each user. You could also choose to encode other parts of your user model in the token as you see fit.
The Final Guardian & GuardianDb Configurations
In your “config/dev.exs” file, define the Guardian config as:
config :guardian, Guardian,
hooks: GuardianDb,
allowed_algos: ["HS512"],
verify_module: Guardian.JWT,
issuer: "localhost",
ttl: { 30, :days },
allowed_drift: 2000,
verify_issuer: true,
secret_key: %{"alg" => "HS512",
"k" => "XyS2ldEAftwz4rvFVkY6LfcDRQpZaM0XVk4bCrrNnrZxn-dFWbyrCm-
0v37Iho0JwV2q-g-i8YyPnQwFw_ittQ",
"kty" => "oct", "use" => "sig"},
serializer: MealheroServer.GuardianSerializerNote: Your secret key will look different than mine!
In your “config/prod.exs” file, define the config as:
config :guardian, Guardian,
hooks: GuardianDb,
allowed_algos: ["HS512"],
verify_module: Guardian.JWT,
issuer: System.get_env("GUARDIAN_ISSUER"),
ttl: { 30, :days },
allowed_drift: 2000,
verify_issuer: true,
secret_key: System.get_env("GUARDIAN_SECRET_KEY"),
serializer: MealheroServer.GuardianSerializerIf you’re interested in what each piece of this config is for, you can check out Guardian’s Github page.
Notice how when in production, I’ve also placed the “issuer” attribute in an environment variable as well as the secret key. The reason for this is that the issuer is used when signing a JWT and is considered to be sensitive data.
Now, let’s add the specific config needed for GuardianDb in the “config/config.exs” file:
config :guardian_db, GuardianDb,
repo: YourAppNameHere.Repo,
schema_name: "guardian_tokens",
sweep_interval: 120 # sweep every 120 minutesThe “schema_name” attribute above relates to the name of the database table we’ll define in the migration file below. The “sweep_interval” indicates how often Guardian should get rid of expired tokens since it doesn’t do so by default.
Last but not least, in your “lib/your_app_name.ex” file, add the GuardianDb worker that will utilize your newly defined “sweep_interval” and take care of removing the expired tokens.
children = [
...
worker(GuardianDb.ExpiredSweeper, [])
]The GuardianDb Migration File
You’ll need to also add a migration file to define the tables in your database that GuardianDb uses. While in your project directory, open your interactive Elixir shell again and generate a migration file using the command:
mix ecto.gen.migration GuardianDbThis will create a new migration file in your “priv/repo/migrations” folder. Open that newly created file and add the following:
defmodule YourAppNameHere.Repo.Migrations.GuardianDb do
use Ecto.Migration def change do
create_if_not_exists table(:guardian_tokens, primary_key: false)
do
add :jti, :string, primary_key: true
add :aud, :string, primary_key: true
add :typ, :string
add :iss, :string
add :sub, :string
add :exp, :bigint
add :jwt, :text
add :claims, :map timestamps
end
end def down do
drop_if_exists table(:guardian_tokens)
end
end
Finally, run the new migration by opening a terminal in the project directory and run the command:
mix ecto.migrateAt this point, you can try running your application. If it runs, then you’ve most likely configured Guardian and GuardianDb correctly! If not, you probably need to run mix deps.getto pull the needed dependencies. Be sure to keep an eye out as I will cover how to use both of these libraries in a separate article coming soon!






