Elixir Phoenix: Creating An App With Tests (Part 4: Using Google Ueberauth)

Brian Emory
Brian Emory | Web Developer
7 min readMay 19, 2017

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).

test/catcasts_web/controllers/session_controller_test.exs

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).

lib/catcasts_web/router.ex

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).

lib/catcasts_web/controllers/session_controller.ex

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).

mix.exs

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.

You may see this screen or a screen similar to the following one
Fill in the project name and click “CREATE”
We should be in the “Credentials” section, click the “OAuth consent screen” tab, enter the project name, select an email from the dropdown box, click “Save”
On the “Credentials” tab, we will select “Create credentials” from the box
Select “OAuth client ID” from the dropdown
Select “Web application” for our type, enter the name, our redirect will be http://localhost:4000/auth/google/callback, click “Create”
We are set up and here are the keys we need

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".

config/config.exs

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.

.env

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).

.gitignore

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).

lib/catcasts_web/controllers/session_controller.ex

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.

To fix this error, in your terminal type source .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).

test/catcasts_web/controllers/session_controller_test.exs

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).

lib/catcasts_web/router.ex

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).

lib/catcasts_web/controllers/session_controller.ex

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).

lib/catcasts_web/controllers/session_controller.ex

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!

Follow me on Twitter @thebrianemory. Follow me here, click the hands below to show some appreciation, leave a comment, and get in touch!

--

--

Brian Emory
Brian Emory | Web Developer

Backend Software Engineer (Ruby/Elixir). Giraffe-like qualities. I enjoy video games, bad movies, hard ciders, and pizza.