User authentication with Guardian & Überauth Photo by on Matt Artz Unsplash This blog post is part of a series of posts detailing the process of . The previous entries have been: development AlloyCI Part 1: Starting a new Project Part 2: Structuring a new Phoenix Project Part 3: Project management with GitHub Issues & Boards One of the first things you need for a non-trivial web application is user management, and authentication. AlloyCI needs to connect to OAuth services, like GitHub, but also let users sign up with email and password. In the Rails ecosystem we have . Devise takes care of the heavy lifting on user management, it encrypts passwords, makes it easy to recover passwords, you can validate user’s emails, before they can use their account, etc., and it also makes it super easy to integrate with , which allows you to let users sign up and login via services that implement OAuth. Devise OmniAuth My first instinct was to search for a similar library like Devise, but for the Phoenix/Elixir ecosystem. I was surprised when I couldn’t find anything as feature rich as Devise. There is a great discussion over at around what people use to authenticate users. This was my first source of information. ElixirForum The first takeaway from that discussion is that there a library very similar to Devise, and that is the by Stephen Pallen, but it’s still in early development, and has no support for OAuth. This was a blocker for me, as OAuth is needed for AlloyCI to communicate with GitHub properly. is Coherence Project The second takeaway was that there seems to be some consensus around what a lot of developers use for authentication, but there are not many resources for beginners to get started properly. The most used approach seems to be a mix between and . Both projects are maintained by the same group of people, so integrating them is not that complicated. Guardian Überauth Guardian Guardian uses JSON Web Tokens to authenticate users and authorize their actions. It provides the entire mechanism to create, encrypt, and validate JWTs. It gives you a way to keep a “session” active for a user, but it does not provide a method to actually manage them. Überauth Überauth allows you to create users from different authentication providers. It is compatible with a lot of OAuth services, and also provides a generic provider called “Identity” that allows your application to create users based on an email/password combination. It gives you the first entry point to authenticate a user, but it has no way of keeping a session active, or authorize actions. Integrating Guardian and Überauth is key in order to get a proper authentication and session management system. The good stuff Now to the good stuff. How do you integrate Guardian & Überauth to an existing Phoenix application? The code inside AlloyCI is based on the , but it was updated to be compatible with Phoenix 1.3.0 and Elixir 1.6.1. example application 1. Migrations You should already have a schema with the fields and . The password and other authorization related information will not be stored in the schema, but under its own schema. Let’s create it: user email name User Authorization This data structure will store information about the user’s authentication methods. (The example app calls them “authorizations”, because Guardian provides the option to encode permissions into the JWT it creates, so technically, you can also use it to authorize user’s actions, and not just authenticate them) With the schema created, we need to modify the schema to tell it it now Authentication User has_many :authentications Under normal circumstances, JWTs cannot be revoked, so if one of your tokens is compromised, you will need to switch your generation/validation function, thus invalidating all existing tokens. Guardian provides a workaround to this. By adding the dependency, Guardian will save your JWTs into the database, where you can easily revoke single tokens, should the need arise. A lot of people argue that this goes against the whole JWT schema, as it turns a session-less authentication schema, into a session-full one, but for the needs AlloyCI has, this approach works perfectly. GuardianDb In order to use you need to create a new database table to hold the token information: GuardianDb 2. Controller Now that we have the schemas ready, we can go ahead with the controllers. The example app already provides us with everything we need, so it’s just a matter of modifying it to fir our needs. This is what I did for AlloyCI initially. The first feature that I added was user login, since it’s the most necessary one for any online service. Let’s start with the authentication controller, since this is the entry point for our authentication logic. As you can see, this controller has actions to present a login page, handle the logout action, handle the callbacks from Überauth, and also generic callbacks for unauthenticated users, and unauthorized actions. The Überauth callbacks handle the information that is sent from the OAuth provider. We use pattern matching in the function signatures to handle both successful and failed OAuth callbacks. The way Überauth works is the following: Registers a Plug that intercepts the raw HTTP request from the OAuth provider Performs actions on the raw information and prepare special for the request assigns Forwards the processed information to the controller Depending on whether the OAuth callback succeeded or failed, the Überauth plug populates the corresponding assign. It is with this that we pattern match the request. If the callback failed, we redirect the user to the login screen and show an error message. If the action succeeded, we delegate to the module to do the actual heavy lifting. assign Accounts This module takes care of reading the information sent by the OAuth provider and prepared by the Überauth Plug and returns a user object. This user could be a new user, or an existing user, and the job of determining which one is returned, falls on the Accounts module. This is handled by the function. You can have a look at the file, to see how everything ties together: get_or_create_user/2 _alloy_ci - Continuous Integration, Deployment, and Delivery coordinator, written in Elixir._github.com AlloyCI/alloy_ci At this point, it is completely up to you how to handle what the application does with the information returned from the OAuth provider. In the case of GitHub, for AlloyCI, we create a new account if one doesn’t exist and populate it with the returned data, i.e. name, email, and username. AlloyCI also allows users to be created via a regular account creation form, in this case, we ask the user for all this information, plus a password and password confirmation. For this to work with Überauth’s authentication procedure, it provides a generic OAuth provider. This provider simulates a proper OAuth request, and sends all the information from the form. The password is then hashed and salted using hash algorithm. Identity Comeonin.Bcrypt If an account exists, then it is validated with the data already in the database. After the user is authenticated, the full application is available to them. The authentication is added to the session as a JWT Token with: Guardian.Plug.sign_in(user, :access, perms: %{default: Guardian.Permissions.max}) 3. Routing In order for the authentication filters to work, they need to be setup both on the controller and on the routes. In Rails, you would add a to your controller and call it a day, but in Phoenix there are two ways to do this, a routing pipeline, or as a controller specific plug. before_filter Route Pipeline A route pipeline allows you to send the request through a set of predefined plugs. The authentication pipeline for AlloyCI is defined in the file like so: [routes.ex](https://github.com/AlloyCI/alloy_ci/blob/master/lib/alloy_ci/web/router.ex#L12-L18) As you can see from the comments, these plugs only check for the correct JWT, but if none is found, they don’t do anything else. This is on purpose, because we have resources that can be accessed without a token. For all other resources that need an authenticated user, an extra plug is needed. This is where the controller specific “filters” come in. Controller Specific Plugs You can define extra plugs on each controller to further filter the request. We use this on AlloyCI for controllers that need an authenticated user, and on the admin specific controllers. For the for example, we use one plug to make sure the user is logged in, and another to change the default layout, but only for the “show” action: [pipeline_controller.ex](https://github.com/AlloyCI/alloy_ci/blob/818d50a00c57a913f830bde74facc26b7c232191/lib/alloy_ci/web/controllers/pipeline_controller.ex#L4-L5) The does not call any of the authentication plugs, since it does not need to have a user object, in order to show public information, like the landing page, or the registration page. public controller What is a Plug anyway? A plug is a specification for composable modules between web applications. They allow you to share information between the modules, and manipulate it along the way. A Plug represents a connection with and it is available throughout the request cycle. You can use a Plug to manipulate the request, extract information from it, add information to it, etc. Plug.Conn In the case the authentication Plugs, we get information from the connection session, validate it, and then add private claims to it. We then use this private claims in another Plug to load the resource (this is all handled by the Guardian library). This is actually what the pipeline we showed you above does. current_user :browser_auth A more basic use of a Plug can be found , where we get information from the connection header, and save it to the assigns inside the connection, in order to be able to pattern match against it: here This plug is declared inside a for the GitHub API handler, and is used in the like this, where we pattern match against a “push” event where a reference was deleted: pipeline github_event_controller.ex 4. Views & Templates 5. Tests How you test your application is totally up to you, but if you want to test controllers that are behind the authentication wall, then you will need some helper methods to prepare the connection and populate it with the structs it needs for the tests: These methods should be added to the file and used within your controller tests, e.g : [conn_case.ex](https://github.com/AlloyCI/alloy_ci/blob/master/test/support/conn_case.ex) [pipeline_controller_test.exs](https://github.com/AlloyCI/alloy_ci/blob/master/test/web/controllers/pipeline_controller_test.exs) This covers the basics of how Guardian and Überauth are integrated with AlloyCI, and should give you a starting point, and some insight, into how to integrate them with your project.