1. Introduction

This tutorial is the continuation of the accounts and session tutorial.

1.1 Installing the oauth plugin

First, we'll install the OAuth plugin:

$ crails plugins extra -a libcrails-oauth libcrails-http-client

The OAuth plugin will need to make HTTP queries to the OAuth services that you'll use, so we also need to require libcrails-http-client along with libcrails-oauth.

2. Google Sign-in

We will start by implementing OAuth with Google as a provider. We'll do it in three steps: creating a controller, adding routes to the router, and add a button on the sign-in view.

However, before we start working on the code, you should create the credentials for your application. Head to Google's developer console, and create an ID clients OAuth 2.0 using the credentials API.

You will be given a Client ID and a Client Secret. You should also add the following redirection URL:
http://localhost:3001/session/oauth/google/callback.

2.1 OAuth controller

You're all set. Now let's implement OAuth in our application. Create the following header file:

app/controllers/google_oauth.hpp
#pragma once
#include <crails/oauth/controller.hpp>
#include <crails/oauth/google.hpp>
#include "application.hpp"
#include "app/models/user.hpp"

typedef Crails::OAuth::Controller<
  User,                  // <- Your authenticable model goes here
  Crails::OAuth::Google, // <- OAuth backend to use
  ApplicationController  // <- Controller to extend. Must implement Crails::Odb::Controller.
> OAuthGoogleControllerBase;

class OAuthGoogleController : public OAuthGoogleControllerBase
{
public:
  OAuthGoogleController(Crails::Context& context) : OAuthGoogleControllerBase(context)
  {}

  // The following methods are pure virtuals from Crails::OAuth::Controller:
  Crails::OAuth::Settings settings() const override;
  std::shared_ptr<User>   find_user(const std::string& email) override;
  std::shared_ptr<User>   create_user(Crails::OAuth::Google& api) override;
  void                    update_user(Crails::OAuth::Google&, User&) override;
};

Now let's implement these pure virtuals methods and see what purpose they serve:

app/controllers/google_oauth.cpp
#include "google_oauth.hpp"
#include "lib/odb/application-odb.hxx"

/*
 * The `settings` method allows you to specify the specific settings
 * for your provider: id, secret, and the url which the provider shall
 * redirect to after authentication:
 */
Crails::OAuth::Settings OAuthGoogleController::settings() const override
{
  return Crails::OAuth::Settings()
    .with_id("123456789012-abcdefghijklmnopqrstuvwxyz012345.apps.googleusercontent.com")
    .with_secret("GOCSPX-abcdefghijklmnopqrstuvwxyz01")
    // Take note of the redirect_url: we'll use it later in the router
    .with_redirect_url("http://localhost:3001/session/oauth/google/callback");
}

/*
 * The `find_user` method must attempt to find a matching user based on
 * the user's email. It will be used to know which user should get
 * connected, or if a new user should be created.
 */
std::shared_ptr<User> OAuthGoogleController::find_user(const std::string& email) override
{
  std::shared_ptr<User> user;

  database.find_one(user, odb::query<User>::name == email);
  return user;
}

/*
 * The `create_user` method must create a new user, based on the data
 * provided by the OAuth API object. The API object provide different
 * methods depending on the provider, but you'll always have at least
 * access to the `get_user_email` method.
 */
std::shared_ptr<User> OAuthGoogleController::create_user(Crails::OAuth::Google& api)
{
  auto user = std::make_shared<User>();

  user->set_name(api.get_user_email());
  return user;
}

/*
 * The `update_user` is called when an existing user connects through
 * OAuth. It gives you the opportunity to update the user's data based
 * on the data received from the OAuth provider.
 */
void OAuthGoogleController::update_user(Crails::OAuth::Google& api, User& user)
{
  // Example:
  // user->set_avatar(api.get_user_avatar());
}

2.2 OAuth routes

Our controller is ready. We will now add the routes to access it. The libcrails-oauth plugin provides the oauth_actions helper, which we will leverage here:

#include "app/controllers/user.hpp"
#include "app/controllers/session.hpp"
#include "app/controllers/google_oauth.hpp" // <- Add our new header
#include <crails/router.hpp>

void Crails::Router::initialize()
{
  oauth_actions("/session/oauth/google", OAuthGoogleController);
  resource_actions(user, UserController);
  signin_actions("/session", ::SessionController);
  match_action("GET", "/session/new", ::SessionController, new_);
}

This will create two routes:

2.3 Sign in button

We're done with the backend. Last thing left to do: create a button in the sign-in view that will allow users to connect with their Google account. Open the new session view and add the following button below the Register link:

app/views/session/new.html
<p>Not a member? <a href="/user/new">Register</a></p>

<!-- from: https://stackoverflow.com/a/59119994/626921 -->
<%= tag("a", {
  {"href","/session/oauth/google"},
  {"class","btn btn-outline-dark"},
  {"style","text-transform:none"}}) yields %>
  <img width="20px" style="margin-bottom:3px; margin-right:5px" src="https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/Google_%22G%22_Logo.svg/512px-Google_%22G%22_Logo.svg.png" />
  Login with Google
<% yields-end %>

And that's it ! Build your application, launch the server, and you'll be able to create new users with your Google account !

2.4 Google OAuth user data

Before moving on to other matters, let's take a look at the user data you can access through Google's OAuth API object, aka Crails::OAuth::Google:

FieldMethod
Emailget_user_email()
Avatarget_user_avatar()
Given nameget_user_firstname()
Family nameget_user_lastname()
Nameget_user_name()
Localeget_user_locale()

3. Microsoft Live

4. Facebook Sign-in

5. Additional featuers

5.1 Filtering user creation

The default behaviour for libcrails-oauth is to create a user account when the user signing in doesn't already have one.

You may wish to disable account creation through OAuth, or to implement your own conditions to figure out whether a new account should or shouldn't be created. This can be done by overloading the is_acceptable_new_user method. Let's do it with our Google OAuth controller:

app/controllers/google_oauth.hpp
...
class OAuthGoogleController : public OAuthGoogleControllerBase
{
public:
  ...
  bool is_acceptable_new_user(Crails::OAuth::Google&) const override { return false; }
};

With this simple overloading, we have disabled account creating from users connecting with Google OAuth. Since we receive the Crails::OAuth::Google object as a parameter, we can also refine this behaviour and authorize account creation only from specific users.