The move from 0.14.x to 1.0 is significant behind the scenes. Although we've tried to keep backwards compatibility where we could, in order to move Guardian forward we've had to make some breaking changes. Guardian is no longer constrained to using only JWT. Although it provides all the JWT behaviour out of the box that it used to, you can now add your own types of tokens.
Dependencies on Phoenix and Plug have been made optional so that Guardian can be used in a more stand-alone manner.
In 0.14.x Guardian was a single, globally unique implementation. In 1.0 you'll need to define a module where all your authentication related items live. You'll also interact with this module rather than directly with Guardian (unless you're a library developer).
The Implementation module is the serializer and provides a place for you to put Hooks
. To create the implementation:
defmodule MyApp.Guardian do
use Guardian, otp_app: :my_app
# ...
end
Configuration has not changed a lot, but the way that you can define config values has become a lot more useful.
Configuration values can be given using the following types:
{MyModule, :func, [:some, :args]}
Calls the function on the module with args- any other value
The old values (assuming you're using JWT) are still present, but there are a few new options.
token_verify_module
- The module to verify claims. DefaultGuardian.Token.Jwt.Verify
token_ttl
- A map of%{<token_type> => <ttl_value>}
Rather than setting configuration for Guardian
directly, use your implementation module.
0.14.x
config :guardian, Guardian,
# options
1.0
config :my_app, MyApp.Guardian,
# options
In 0.14.x you defined a serializer. The two functions from_token
, and for_token
have been renamed and moved. In 0.14.x you'd create a serializer with these two functions.
The new functions are subject_for_token
and resource_from_claims
. These functions are more clearly named and receive more complete arguments.
defmodule MyApp.Guardian do
use Guardian, otp_app: :my_app
def subject_for_token(resource, _claims) do
{:ok, to_string(resource.id)}
end
def subject_for_token(_, _) do
{:error, :reason_for_error}
end
def resource_from_claims(claims) do
{:ok, find_me_a_resource(claims["sub"])}
end
def resource_from_claims(_claims) do
{:error, :reason_for_error}
end
end
The options for the use
call require an :otp_app
specification. You can also include your configuration that does not change across environments here if you choose.
Was:
Guardian.encode_and_sign(resource, [token_type, claims])
Guardian.decode_and_verify(token, [claims_to_check])
Becomes:
MyApp.Guardian.encode_and_sign(resource, [claims, options])
MyApp.Guardian.decode_and_verify(token, [claims_to_check, options])
Where options are specified in the Guardian.Token.Jwt module
0.14.x
Guardian.encode_and_sign(resource, "other_type")
1.0
MyApp.Guardian.encode_and_sign(resource, %{}, token_type: "other_type")
0.14.x
Guardian.encode_and_sign(resource, %{ttl: {1, :hour}})
1.0
MyApp.Guardian.encode_and_sign(resource, %{}, ttl: {1, :hour})
0.14.x
Guardian.encode_and_sign(resource, %{secret: "some_secret"})
1.0
MyApp.Guardian.encode_and_sign(resource, %{}, secret: resolvable_config_value)
0.14.x
{:ok, new_token, new_claims} = Guardian.exchange(old_token, "refresh", "access")
1.0
{:ok, {old_token, old_claims}, {new_token, new_claims}} =
MyApp.Guardian.exchange(old_token, ["refresh"], "access")
0.14.x
{:ok, new_token, new_claims} = Guardian.refresh!(old_token, claims_to_check, options)
1.0
{:ok, {old_token, old_claims}, {new_token, new_claims}} =
MyApp.Guardian.refresh(old_token, opts)
0.14.x
:ok = Guardian.revoke!(token, claims, opts)
1.0
{:ok, old_claims} = MyApp.Guardian.revoke(old_token, opts)
When working with plugs, sign_in
/sign_out
are still with us but instead of using the Guardian Module directly use your own implementation
0.14.x
conn = Guardian.Plug.sign_in(conn, resource, token_type, claims)
conn = Guardian.Plug.sign_out(conn, token, [claims_to_check])
1.0
conn = MyApp.Guardian.Plug.sign_in(conn, resource, claims, opts)
conn = MyApp.Guardian.Plug.sign_out(conn, resource, [claims_to_check, opts])
To use Guardian with Plugs you'll now need to define a pipeline. A pipeline is a plug that
- Sets the implementation module to use
- Sets the error handler to use
- Adds the plug pipeline to use
defmodule MyApp.Guardian.AuthPipeline do
@claims %{typ: "access"}
use Guardian.Plug.Pipeline, otp_app: :my_app,
module: MyApp.Guardian,
error_handler: MyApp.Guardian.AuthErrorHandler
plug Guardian.Plug.VerifySession, claims: @claims
plug Guardian.Plug.VerifyHeader, claims: @claims, realm: "Bearer"
plug Guardian.Plug.EnsureAuthenticated
plug Guardian.Plug.LoadResource, ensure: true
end
Use this as a plug to:
- Lookup the token in either the session or header
- Verify the token, it must be of type "access"
- Ensure we found a token
- Load the resource for the token and make sure one was found
You'd use this in your Phoenix router.
The error handler modules have had a makeover. Previously when you used one of Guardian's plugs you would specify a module with a number of callbacks
0.14.x
already_authenticated(Plug.Conn.t, map) :: Plug.Conn.t
unauthenticated(Plug.Conn.t, map) :: Plug.Conn.t
unauthorized(Plug.Conn.t, map) :: Plug.Conn.t
no_resource(Plug.Conn.t, map) :: Plug.Conn.t
In 1.0 error handler modules have been simplified. There is now a single function required
auth_error(conn, {failure_type, reason}, opts)
The failure types that come out of the box are:
:invalid_token
:unauthorized
:unauthenticated
:already_authenticated
:no_resource_found
0.14.x required any custom claims to be specified when you create the token either with sign_in
or encode_and_verify
. 1.0 still allows specifying literal claims inline but also provides a hook in your implementation module.
defmodule MyApp.Guardian do
use Guardian, otp_app: :my_app
def build_claims(claims, resource, opts) do
# opts is passed directly from the caller
new_claims = do_things_to_claims(claims)
{:ok, claims}
end
end
The hooks module has been removed. Rather than this there are callbacks defined on your implementation. These are noops by default. See the Guardian
module documentation for more information.
after_encode_and_sign
after_sign_in
before_sign_out
on_verify
on_revoke
on_refresh
on_exchange
build_claims
The macros that were provided for Phoenix sockets and channels have been removed. There is now only a lightweight set of functions to help store resource/claims/token on the socket.
Guardian.Phoenix.Socket.authenticated?
- check if the socket has been authenticatedGuardian.Phoenix.Socket.authenticate
- Sign in a resource to a socket. Sets token/claims/resource on the socket.Guardian.Phoenix.Socket.current_claims
Guardian.Phoenix.Socket.current_token
Guardian.Phoenix.Socket.current_resource
0.14.x
defmodule MyApp.UserSocket do
use Phoenix.Socket
use Guardian.Phoenix.Socket
## Channels
channel "user:*", MyApp.UserChannel
## Transports
transport :websocket, Phoenix.Transports.WebSocket
# transport :longpoll, Phoenix.Transports.LongPoll
def connect(_params, _socket) do
:error
end
def id(socket), do: "users_socket:#{current_resource(socket).id}"
end
1.0
defmodule MyApp.UserSocket do
use Phoenix.Socket
## Channels
channel "user:*", MyApp.UserChannel
## Transports
transport :websocket, Phoenix.Transports.WebSocket
# transport :longpoll, Phoenix.Transports.LongPoll
def connect(%{"guardian_token" => token}, socket) do
case Guardian.Phoenix.Socket.authenticate(socket, MyApp.Guardian, token) do
{:ok, authed_socket} ->
{:ok, authed_socket}
{:error, _} ->
:error
end
end
def connect(_params, _socket) do
:error
end
def id(socket), do: "users_socket:#{Guardian.Phoenix.Socket.current_resource(socket).id}"
end
Permissions are not always active anymore. These have become optional and renamed to be more clear.
See Guardian.Permissions.Bitwise
for more information
To use permissions (Bitwise), in your implementation module use the bitwise module and then utilize the build_claims
hook.
Permissions can be defined as a list - using positional bits, or via a Map where the value is the bit location.
defmodule MyApp.Guardian do
use Guardian, otp_app: :my_app,
permissions: %{
user_actions: %{
books: 0b1,
fitness: 0b100,
music: 0b1000,
}
}
use Guardian.Permissions.Bitwise
# snip
def build_claims(claims, _resource, opts) do
claims =
claims
|> encode_permissions_into_claims!(Keyword.get(opts, :permissions))
{:ok, claims}
end
end
end
In this case, when creating the token the options are passed to build claims as-is. So to call this it would look like.
MyApp.Guardian.encode_and_sign(resource, %{}, permissions: %{user_actions: [:books, :fitness]})
To interrogate permissions:
# Get the encoded permissions from the claims
found_perms = MyApp.Guardian.decode_permissions_from_claims(claims)
# Check if all permissions are present
has_all_these_things? =
claims
|> MyApp.Guardian.decode_permissions_from_claims(claims)
|> MyApp.Guardian.all_permissions?(%{user_actions: [:books]})
# Checks if any permissions are present
show_any_media_things? =
claims
|> MyApp.Guardian.decode_permissions_from_claims(claims)
|> MyApp.Guardian.any_permissions?(%{user_actions: [:books, :fitness, :music]})
The Guardian.Plug.EnsurePermissions
Plug has been removed. Instead use the Guardian.Plug.Bitwise
module. This must be used downstream of a Guardian Pipeline
plug Guardian.Permissions.Bitwise, ensure: %{user_actions: [:books]}
A one_of
feature is supported where you can give a list of possible permission configurations. If one of them matches, the permission will be considered granted.
plug Guardian.Permissions.Bitwise, one_of: [
%{default: [:public_profile], user_actions: [:books]},
%{default: [:public_profile], user_actions: [:music]},
]
In Guardian 0.14 it was allowed to have a null token in the authorization header, this is no longer allowed, see this issue for more information