Elixir Phoenix: Creating An App With Tests (Part 4: Using Google Ueberauth)
Updated: January 16th, 2019
Sign in with Google (GitHub)
We are going to have users sign in with Google to use our web app. We could use Twitter, GitHub, etc. but I decided to go with Google since we will be using YouTube videos. We will be using Ueberauth to take care of this for us.
We will be taking a TDD approach in this section. We will write our test, run mix test
to see what happens, and write just enough code to clear the error we receive. We are going to create a test which will confirm our /auth/google
route is hitting Google and being redirected to allow the user to sign in. Later, we will create a test to mock the response from Google and ensure our user is being created.
We will first create a file called session_controller_test.exs
and add it to the directory test/catcasts_web/controllers
(fa0f402).
If we run mix test
we will see that it is failing because we have not created a route for GET /auth/google
. To do that, we will create a scope "/auth"
block in our router.ex
to handle our authentication with Google routes. This will be in addition to the scope "/"
block we already have in the router.
We will open up our lib/catcasts_web/router.ex
to add our scope. By using :provider
instead of google
, we can use the same route for any authentication solution we want to add in the future. For example, if we wanted to add authenticating with Twitter , /auth/twitter
would work without needing to add another route (bb6e814).
Running mix test
we see our next error is telling us that our SessionController
is not available. This is because it does not yet exist. We will create it next (cd24467).
Running mix test
tells us SessionController.request/2
is undefined or private. This is handled by Ueberauth so we will add and configure it next.
In the mix.exs
in the app’s root directory, we will add ueberauth
, ueberauth_google
, and poison
to our defp deps
block (8a20426).
We can install our new dependencies using mix deps.get
(you will need to restart your server after changes to mix.exs
).
Our next step will be to set up our application with Google by visiting the Google Developer Console. Setting up your app with Google can be a little tricky. As long as nothing has changed, if you follow the screenshots we should get everything created with no issues. Otherwise, you may have to look around for similarly sounding sections. However, if you have any trouble leave a comment below.
Hold on to those keys, we will be using them shortly.
Inside our config/config.exs
, we will start the process of configuring our application to authenticate with Google. This will go above the import_config “#{Mix.env}.exs”
line (42b8611).
Note: If you view this commit, you will see it has the default_scope
as default_scope: "emails profile plus.m"
. This is a typo brought over from the Ueberauth readme. It is corrected in the gist below. The correct one is default_scope: "email profile plus.m"
.
If your server is running, you will need to restart it since we made changes to our config.exs
.
We can now set up a .env
file to hold the keys we received when we registered our application with Google. In the root of our app, create the .env
file. Add the following two lines but put in your keys in the place of your_google_client_id
and your_google_client_secret
.
If you have created a git repository, we do not want these to be uploaded with our code. We will open up our .gitignore
file and add .env
at the bottom (604e7dc).
Now, to get our local environment variables to work, in your terminal type source .env
(you will need to do this for each terminal window you are using). We now have all of our configuration setup for using Ueberauth.
We will now need to update our SessionController
to add the Ueberauth plug (29f07d3).
If we run mix test
now, we will see our test is passing. Make sure you have ran source .env
in the terminal window before you run mix test
.
If you visit http://localhost:4000/auth/google?scope=email%20profile, you may see an error like below. If at any point you see an error telling you that you are missing the required parameter client_id, all you will need to do is type source .env
in your terminal window (this may mean stopping your server and typing it in there). It has to be done for each terminal window that is trying to access the keys we put inside .env
.
In our test, we used the verbose link http://localhost:4000/auth/google?scope=email%20profile. While we could just use /auth/google
, our link tells Google we want the email and profile information (like the user’s name). We need that information for creating our users’ accounts.
You can start up your server, go to http://localhost:4000/auth/google?scope=email%20profile and you should see a similar Google prompt like the one below.
If you choose your account, we get redirected back to our app with an error.
no route found for GET /auth/google/callback (CatcastsWeb.Router)
After you choose the account you want to use, Google sends us back to /auth/google/callback
which is a route we have not yet created. This route will take the user information from Google to create our user. We will create a test in the session_controller_test
for that now and get it passing in the next section. (853e84a).
We are using @ueberauth_auth
to mock out the response we get back from Google. We can test that our /auth/google
path redirects to Google, and we can test the callback /auth/google/callback
, but we cannot test what happens on the Google side of things. Our mocked data mirrors the important parts of what we get back from Google. This allows us to test our SessionController
's new
and create
actions just as they will be used in our application.
Running mix test
we will see that we need to create our /auth/google/callback
route next inside our router.ex
(144858c).
Running mix test
again will tell us that our SessionController.create/2
is undefined or private. We will add our create/2
as well as alias Catcasts.User
so we can just use User
instead of typing out Catcasts.User
every time (058bc3f).
Running mix test
we see User.__struct__/1 is undefined, cannot expand struct User
which means it’s time to set up our users.
Setting up users
We are going to create users in a bit of a non-traditional way. Instead of creating an Accounts context to hold our users and a users_controller
to handle the creation of our users, we are going to generate a User
schema and hit that from our session_controller
. This is a little unconventional but it will be fine for the purposes of our simple app.
We create our User
schema by running mix phx.gen.schema User users first_name:string last_name:string email:string provider:string token:string
. This gives us a user.ex
file and a migration file for our users. If you open up either file, you will see all the information we need is already set up thanks to the command we ran (0fa38b8).
We can run mix ecto.migrate
to create the users table in our database.
When a user hits the link to sign in with Google, chooses their account, and is redirected back to our application, that redirect hits the create
action in the SessionController
. At this point we should create a new user or if a user is already created, sign them in. Let’s update our create
action and create a new private function called create_or_sign_in_user
to handle this.
Below you will see we updated our alias to include Repo
, added a case
statement to the create
action, and added our new private function (2a442bd).
Now if we run mix test
they will all be passing as our users can be created. We are not testing anything related to a failure to create a user. Since it is being created with information we receive from the user’s Google profile, we are operating on the presumption that Google would not allow their account to be created without all the necessary information.
You can go to http://localhost:4000/auth/google?scope=email%20profile and try it out!
Part 1: The Setup
Part 2: Using Tailwind CSS
Part 3: Navbar, Errors, and Homepage
Part 4: Using Google Ueberauth
Next Up:
Part 5: Setting a Current User
Part 6: Using the YouTube API
Part 7: Setting Up Authorizations
Part 8: Making Things Look Nicer
Part 9: Search, Sort, Pagination
Part 10 Bonus: Using Elixir’s 1.6 Code Formatter
Follow me on Twitter @thebrianemory. Follow me here, click the hands below to show some appreciation, leave a comment, and get in touch!