Upgrade guide 0.14.x to 1.0
View SourceThe 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.
Implementation
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
# ...
endConfiguration
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.Verifytoken_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,
# options1.0
config :my_app, MyApp.Guardian,
# optionsSerializers
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
endThe 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.
Creating tokens
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
Token types
0.14.x
Guardian.encode_and_sign(resource, "other_type")1.0
MyApp.Guardian.encode_and_sign(resource, %{}, token_type: "other_type")Setting TTL
0.14.x
Guardian.encode_and_sign(resource, %{ttl: {1, :hour}})1.0
MyApp.Guardian.encode_and_sign(resource, %{}, token_ttl: {1, :hour})Custom Secrets
0.14.x
Guardian.encode_and_sign(resource, %{secret: "some_secret"})1.0
MyApp.Guardian.encode_and_sign(resource, %{}, secret: resolvable_config_value)Exchange
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")Refresh
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)Revoke
0.14.x
:ok = Guardian.revoke!(token, claims, opts)1.0
{:ok, old_claims} = MyApp.Guardian.revoke(old_token, opts)Sign In/Sign Out
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])Pipelines
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, allow_blank: false
endUse 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.
Error handler
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.tunauthenticated(Plug.Conn.t, map) :: Plug.Conn.tunauthorized(Plug.Conn.t, map) :: Plug.Conn.tno_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
Custom Claims
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
endHooks
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_signafter_sign_inbefore_sign_outon_verifyon_revokeon_refreshon_exchangebuild_claims
Sockets/Channels
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_claimsGuardian.Phoenix.Socket.current_tokenGuardian.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}"
end1.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}"
endPermissions
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
endIn 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]})Usage with Plug
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.
Allow the request to continue when the token contains any of the permission sets specified
plug Guardian.Permissions.Bitwise, one_of: [
%{default: [:public_profile], user_actions: [:books]},
%{default: [:public_profile], user_actions: [:music]},
]