How to Authenticate your Elixir/Phoenix APIs using Guardian
Building secure, authenticated APIs is a critical part of modern web development. As an Elixir developer, you have some great tools at your disposal to implement API authentication quickly and easily in your Phoenix applications. In this guide, we‘ll walk through the process of adding token-based authentication to a Phoenix API using the Guardian library.
Why is API Authentication Important?
APIs often provide access to sensitive user data and actions, so it‘s crucial to ensure only authorized users and clients can access protected resources. Requiring authentication for your API endpoints helps:
- Safeguard user privacy by restricting access to authorized parties
- Prevent data breaches and maintain security of your systems
- Control usage and prevent API abuse or unintended uses
- Provide personalized, user-specific data and functionality
While there are several approaches to API authentication, using tokens is a popular and effective method. The general idea is that upon successful login, the server generates a unique access token which is then included by the client with each subsequent request to authenticate.
Authentication Libraries in Elixir
To add API authentication in Phoenix, we‘ll use a pair of powerful Elixir libraries:
-
Ueberauth – Allows users to authenticate with your Elixir app using external identity providers like Google, Facebook, Twitter etc.
-
Guardian – Provides a full token-based authentication solution, handling things like token generation, session storage, and permission management.
Together, Ueberauth and Guardian make it straightforward to implement secure, standards-based authentication in your Elixir APIs. Ueberauth provides an easy way for users to login or signup, while Guardian takes care of managing the session and authentication status.
Setting Up Dependencies
To get started, let‘s add the necessary dependencies to our Phoenix application. Open up your mix.exs file and add ueberauth, guardian and jsonapi to the deps:
defp deps do
[
# ...
{:ueberauth, "~> 0.4"},
{:guardian, "~> 1.0"},
{:jsonapi, "~> 0.7.0"},
]
end
We‘ll also add ueberauth to the list of our app‘s dependencies:
def application do
[
extra_applications: [:logger, :runtime_tools, :ueberauth]
]
end
Now, run mix deps.get
to install the new packages.
Configuring Ueberauth and Guardian
Next, we need to configure Ueberauth and Guardian in our app. For this example, we‘ll use Google OAuth for authentication.
Open config/config.exs
and add this Ueberauth config:
config :ueberauth, Ueberauth,
providers: [
google: {Ueberauth.Strategy.Google, []}
]
config :ueberauth, Ueberauth.Strategy.Google.OAuth,
client_id: System.get_env("GOOGLE_CLIENT_ID"),
client_secret: System.get_env("GOOGLE_CLIENT_SECRET")
This sets up the Google OAuth strategy and pulls the app credentials from environment variables, which is more secure than hardcoding them.
For Guardian, we‘ll use its default JWT tokens and include some standard config:
config :my_app, MyApp.Guardian,
issuer: "my_app",
secret_key: "Secret key. You can use `mix guardian.gen.secret` to get one"
config :my_app, MyApp.AuthPipeline,
module: Guardian,
error_handler: MyApp.AuthErrorHandler
Generate a secret key using the mix guardian.gen.secret
command as instructed. This key will be used to sign the tokens.
The Authentication Controller
With our auth libraries configured, we can implement the core authentication logic in a dedicated AuthController.
defmodule MyApp.AuthController do
use MyAppWeb, :controller
plug Ueberauth
alias MyApp.{User, UserToken}
def callback(%{assigns: %{ueberauth_auth: auth}} = conn, _params) do
case User.find_or_create(auth) do
{:ok, user} ->
{:ok, token, _claims} = UserToken.encode_and_sign(user)
render(conn, "show.json", user: user, token: token)
{:error, reason} ->
render(conn, "error.json", message: reason)
end
end
def callback(%{assigns: %{ueberauth_failure: _fails}} = conn, _params) do
render(conn, "error.json", message: "Failed to authenticate.")
end
end
This controller handles the OAuth callback after a user authorizes with Google. It uses the Ueberauth data to either fetch an existing user record or create a new one.
If successful, it generates a JWT token for the user with Guardian which is returned in the response. The token should be included by clients in the Authorization header of subsequent requests to authenticate.
Protecting Routes & Pipelines
To make use of our authentication system, we need to create a pipeline that will enforce authentication for protected routes. In Phoenix, we can define pipelines in the Router.
pipeline :api do
plug :accepts, ["json"]
end
pipeline :api_auth do
plug Guardian.Plug.Pipeline,
module: MyApp.Guardian,
error_handler: MyApp.AuthErrorHandler
plug Guardian.Plug.VerifyHeader
plug Guardian.Plug.EnsureAuthenticated
plug Guardian.Plug.LoadResource, allow_blank: true
end
scope "/api", MyApp do
pipe_through [:api, :api_auth]
# Protected routes here
end
Our :api_auth
pipeline uses several Guardian plugs to ensure requests are authenticated with a valid token. We can apply it to any scopes or routes that require authentication.
Accessing the Current User
Within authenticated routes, you can access the current user through the Guardian.Plug.current_resource/1
function.
For example, to fetch the current user in a controller:
defmodule MyApp.MyController do
use MyAppWeb, :controller
def show(conn, _params) do
user = Guardian.Plug.current_resource(conn)
render(conn, "show.json", user: user)
end
end
By simply adding the :api_auth
pipeline to this controller‘s routes, we can ensure only authenticated requests can access it, and easily get the current user making the request.
Testing the Authenticated API
To verify everything is working, we can test our API using a tool like Postman or curl. Start your phoenix server with mix phx.server
.
First, make a request to the Ueberauth endpoint to initiate the Google OAuth flow:
GET /auth/google
You should be redirected to Google to authorize the application. Once you approve, you‘ll be redirected back to our AuthController callback, which will respond with the user data and an access token.
{
"access_token": "eyJhbGciOiJIUzUxNiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 3600,
"user": {
"id": 1,
"email": "[email protected]",
...
}
}
Now, include this token in the Authorization header of API requests:
Authorization: Bearer eyJhbGciOiJIUzUxNiIsInR5cCI6IkpXVCJ9...
Authenticated endpoints should now be accessible, while unauthenticated requests will receive a 401 Unauthorized response.
Security Best Practices
When implementing API authentication, it‘s important to follow security best practices to harden your application. Some key considerations:
- Keep your secret keys safe and never expose them client-side or in version control. Use environment variables or a secrets manager.
- Implement cross-site request forgery (CSRF) protection if your app also has a frontend
- Set an appropriate expiration time on tokens and allow them to be revoked if needed
- Use HTTPS everywhere to encrypt data in transit and prevent man-in-the-middle attacks
- Carefully manage your OAuth app credentials and be mindful of the scopes/permissions you request
Advanced Topics & Next Steps
We‘ve covered the essentials of setting up API authentication in Phoenix with Guardian, but there‘s plenty more to dive into!
Some additional topics to explore:
- Refresh tokens for issuing new access tokens
- Handling token expiration and revocation
- User roles and authorization levels
- Two-factor auth or additional user verification
- Supporting additional OAuth providers
The Ueberauth and Guardian docs provide many more details on these advanced features and how to customize your app‘s authentication system.
Conclusion
Adding secure authentication to your Phoenix APIs doesn‘t have to be a daunting task. With the help of Elixir packages like Ueberauth and Guardian, we can implement a robust token-based authentication solution without too much hassle.
By simply installing the dependencies, setting up some config, and creating an AuthController to issue tokens, we can lock down our API endpoints to authenticated users only. We saw how to apply Guardian plugs to our router pipelines to enforce authentication, and access the current user within our controllers.
I hope this guide has given you a solid foundation for authenticating APIs in your own Elixir applications. Remember to follow security best practices, and don‘t be afraid to explore more advanced auth features as your app grows!