View Source Guardian.DB (Guardian.DB v3.0.0)

Guardian.DB is a simple module that hooks into Guardian to prevent playback of tokens.

In Guardian, tokens aren't tracked so the main mechanism that exists to make a token inactive is to set the expiry and wait until it arrives.

Guardian.DB takes an active role and stores each token in the database verifying it's presence (based on it's jti) when Guardian verifies the token. If the token is not present in the DB, the Guardian token cannot be verified.

Provides a simple database storage and check for Guardian tokens.

  • When generating a token, the token is stored in a database.
  • When tokens are verified (channel, session or header) the database is checked for an entry that matches. If none is found, verification results in an error.
  • When logout, or revoking the token, the corresponding entry is removed

Setup

Config

Add your configuration to your environment files. You need to specify

  • repo

You may also configure

  • prefix - The schema prefix to use.
  • schema_name - The name of the schema to use. Default "guardian_tokens".
  • sweep_interval - The interval between db sweeps to remove old tokens. Default 60 (minutes).

Sweeper

In order to sweep your expired tokens from the db, you'll need to add Guardian.DB.Sweeper to your supervision tree. In your supervisor add it as a worker

worker(Guardian.DB.Sweeper, [interval: 60])

Migration

Guardian.DB requires a table in your database. Create a migration like the following:

  create table(:guardian_tokens, primary_key: false) do
    add(:jti, :string, primary_key: true)
    add(:typ, :string)
    add(:aud, :string)
    add(:iss, :string)
    add(:sub, :string)
    add(:exp, :bigint)
    add(:jwt, :text)
    add(:claims, :map)
    timestamps()
  end

Guardian.DB allow to use a custom schema name when creating the migration. You can configure the schema name from config like the following:

config :guardian, Guardian.DB,
  schema_name: "my_custom_schema

And when you run mix guardian.db.gen.migration it'll generate the following migration:

  create table(:my_custom_schema, primary_key: false) do
    add(:jti, :string, primary_key: true)
    add(:typ, :string)
    add(:aud, :string)
    add(:iss, :string)
    add(:sub, :string)
    add(:exp, :bigint)
    add(:jwt, :text)
    add(:claims, :map)
    timestamps()
  end

Guardian.DB works by hooking into the lifecycle of your token module.

You'll need to add it to

  • after_encode_and_sign
  • on_verify
  • on_revoke

For example:

defmodule MyApp.AuthTokens do
  use Guardian, otp_app: :my_app

  # snip...

  def after_encode_and_sign(resource, claims, token, _options) do
    with {:ok, _} <- Guardian.DB.after_encode_and_sign(resource, claims["typ"], claims, token) do
      {:ok, token}
    end
  end

  def on_verify(claims, token, _options) do
    with {:ok, _} <- Guardian.DB.on_verify(claims, token) do
      {:ok, claims}
    end
  end

  def on_revoke(claims, token, _options) do
    with {:ok, _} <- Guardian.DB.on_revoke(claims, token) do
      {:ok, claims}
    end
  end
end

Summary

Functions

After the JWT is generated, stores the various fields of it in the DB for tracking. If the token type does not match the configured types to be stored, the claims are passed through.

When a token is refreshed, we invalidate the old token and add the new token in the DB.

When logging out, or revoking a token, removes from the database so the token may no longer be used.

When a token is verified, check to make sure that it is present in the DB. If the token is found, the verification continues, if not an error is returned. If the type of the token does not match the configured token storage types, the claims are passed through.

Revoke all tokens of a given subject. Returns the amount of tokens revoked.

Functions

Link to this function

after_encode_and_sign(resource, type, claims, jwt)

View Source

After the JWT is generated, stores the various fields of it in the DB for tracking. If the token type does not match the configured types to be stored, the claims are passed through.

When a token is refreshed, we invalidate the old token and add the new token in the DB.

When logging out, or revoking a token, removes from the database so the token may no longer be used.

When a token is verified, check to make sure that it is present in the DB. If the token is found, the verification continues, if not an error is returned. If the type of the token does not match the configured token storage types, the claims are passed through.

Revoke all tokens of a given subject. Returns the amount of tokens revoked.

Usage

Add to your Guardian module.

defmodule MyApp.AuthTokens do
  use Guardian, otp_app: :my_app

  # snip...

  def revoke_all(resource, claims) do
    with {:ok, sub} <- subject_for_token(resource, claims) do
      Guardian.DB.revoke_all(resource)
    end
  end
end

Then you revoke all tokens of a resource.

MyApp.AuthTokens.revoke_all(resource, %{})