How To Integrate Google Calendar API and friendship with Laravel. Part 2

In the previous article we created a project in the Google Cloud Console, as well as configured the access keys through the API.

In this article we'll create a project in Laravel that will authorize through Google and save access token to database. You will learn the basic queries to Google via the OAuth 2.0 protocol.

Let's skip creating the basic skeleton of a Laravel framework and start immediately with creating code to work with the Google API.

Creating the application

First, we need to install the necessary libraries to work with the API. The library allows us to work with Google API Services without unnecessary actions. You can also use a simple Guzzle or CURL client.

composer require google/apiclient

The next step is to add the following variables to your .env file and add configuration to services.php:


GOOGLE_CLIENT_ID= #from .json file
GOOGLE_CLIENT_SECRET= #from .json file
GOOGLE_REDIRECT_URI= #from .json file
GOOGLE_REDIRECT_CALLBACK=https://localhost/oauth2 #redirect URL after fetching userinfo

return [
    'google' => [
        'client_id' => env('GOOGLE_CLIENT_ID'),
        'client_secret' => env('GOOGLE_CLIENT_SECRET'),
        'redirect_uri' => env('GOOGLE_REDIRECT_URI'),
        'redirect_callback' => env('GOOGLE_REDIRECT_CALLBACK'),
        'scopes' => [
        'approval_prompt' => env('GOOGLE_APPROVAL_PROMPT', 'force'),
        'access_type' => env('GOOGLE_ACCESS_TYPE', 'offline'),
        'include_granted_scopes' => true,

OAuth process

After creating and setting up our service, we need to log in through the OAuth process. To do that, we need to generate auth URL and redirect the user to the Google OAuth 2.0 server to begin the authentication and authorization process.

OAuth process

You can use OAuth 2.0 Scopes for Google APIs: Google Calendar OAuth 2 scopes The first column indicates the name of the scope, a list of which we have defined in the settings. Google will require consent for each when you authorize.

In our services.php, we requested permission to:


Google as a Driver

We'll create a service to work with the Google API that will complement and encapsulate the way the app works and add some polymorphism to it.

A little later, we will expand the work of calendars to other providers, such as Outlook.

Create a Marker Interface (Tag Interface). In the future we will supplement it with the necessary methods.

Marker Interfaces are empty interfaces, i.e, they do not have any variables or methods declared in them.

interface ProviderInterface {}

Polymorphism will help us create an adaptive application.

Let's create a basic service to work with all popular calendars.

abstract class AbstractProvider implements ProviderInterface
    protected $providerName;
    protected $request;
    protected $httpClient;
    protected $clientId;
    protected $clientSecret;
    protected $redirectUrl;
    protected $scopes = [];
    protected $scopeSeparator = ' ';
    protected $user;

     * Create a new provider instance.
    public function __construct(Request $request, string $clientId, string $clientSecret, string $redirectUrl, array $scopes = [])
        $this->request = $request;
        $this->clientId = $clientId;
        $this->redirectUrl = $redirectUrl;
        $this->clientSecret = $clientSecret;
        $this->scopes = $scopes;

     * @return RedirectResponse
     * @throws \Exception
    public function redirect(): RedirectResponse
        $this->request->query->add(['state' => $this->getState()]);

        if ($user = $this->request->user()) {
            $this->request->query->add(['user_id' => $user->getKey()]);

        return new RedirectResponse($this->createAuthUrl());

     * @return User
    public function getUser(): User
        if (isset($this->user)) {
            return $this->user;

        try {
            $credentials = $this->fetchAccessTokenWithAuthCode(
                $this->request->get('code', '')

            $this->user = $this->toUser($this->getBasicProfile($credentials));
        } catch (\Exception $exception) {
            throw new \InvalidArgumentException($exception->getMessage());

        $state = $this->request->get('state', '');

        if (isset($state)) {
            $state = Crypt::decrypt($state);

        return $this->user
                explode($this->getScopeSeparator(), $credentials['scope'])

    abstract protected function createAuthUrl();
    abstract protected function fetchAccessTokenWithAuthCode(string $code);
    abstract protected function getBasicProfile($credentials);
    abstract protected function toUser($userProfile);

Our first implementation will be a service for the work of Google, let's create it.

class GoogleProvider extends AbstractProvider
    protected $providerName = 'google';

    public function createAuthUrl(): string
        return $this->getHttpClient()->createAuthUrl();

    public function redirect(): RedirectResponse
        if ($redirectCallback = config('')) {
            $this->request->query->add(['redirect_callback' => $redirectCallback]);

        return parent::redirect();

    protected function fetchAccessTokenWithAuthCode(string $code): array
        return $this->getHttpClient()->fetchAccessTokenWithAuthCode($code);

     * @return array
    protected function getBasicProfile($credentials)
        $jwt = explode('.', $credentials['id_token']);

        // Extract the middle part, base64 decode it, then json_decode it
        return json_decode(base64_decode($jwt[1]), true);

     * @param Userinfo $userProfile
     * @return void
    protected function toUser($userProfile)
        return tap(new User(), function ($user) use ($userProfile) {

     * @return Client
    protected function getHttpClient(): Client
        if (is_null($this->httpClient)) {

            $this->httpClient = new \Google\Client();

            // Add request query to the state

        return $this->httpClient;

The only thing missing is a manager to work with our service. Now we can safely inherit from our base class AbstractProvider and implement new drivers.

use Illuminate\Support\Manager;

class CalendarManager extends Manager 
    protected function createGoogleDriver(): ProviderInterface
        $config = $this->config->get('');

        return $this->buildProvider(GoogleProvider::class, $config);

    protected function buildProvider($provider, $config): ProviderInterface
        return new $provider(

The business logic for getting the data about the Google user and access tokens is almost ready. All that remains is to test it in a live environment.

All that's left to do is make our services accessible by creating new routes and controllers.

Route::name('oauth2.auth')->get('/oauth2/{provider}', [AccountController::class, 'auth']);
Route::name('oauth2.callback')->get('/oauth2/{provider}/callback', [AccountController::class, 'callback']);

The provider parameter will be dynamically inserted and checked against the CalendarManager class.

Let's start by redirecting the user to the Google consent screen using AccountController@auth() function. Click on the link https://localhost/oauth2/google/.

    public function auth(string $driver): RedirectResponse
        try {
            return app(CalendarManager::class)->driver($driver)->redirect();
        } catch (\InvalidArgumentException $exception) {

            abort(400, $exception->getMessage());

The CalendarManager will find the right provider from the URL and create the necessary driver to work with Google and will send a request for an authorization grant. (Step 1,2) OAuth2 flow As soon as the user logs in and agrees with the scopes of our app (reading profile, email, calendars, events), they are redirected back to our AccountController@callback, which we specified in the .env file, we get a code in the body of our response from the Google Auth server.

The getUser() method, based on the code will request an access token and a refresh token with which we can retrieve the data.

public function getUser():

$credentials = $this->fetchAccessTokenWithAuthCode(
    $this->request->get('code', '')

Next, the access token allows you to request private info (step 5,6). At the first request we need to get information about the owner of the account, his name, email and ID.

$this->user = $this->toUser($this->getBasicProfile($credentials));

We encode the access token and refresh tokens via jwt, and store them in the database along with the profile data. The JWT token will store all token validity information.

public function encode(array $payload): string
        $config = config('app');

        $tokenId = base64_encode(random_bytes(16));
        $issuedAt = new \DateTimeImmutable();

        $jwtPayload = [
            'iat'  => $issuedAt->getTimestamp(),
            'jti'  => $tokenId,
            'iss'  => $config['name'],
            'nbf'  => $issuedAt->getTimestamp(),
            'exp'  => $payload['expires_at']->getTimestamp(),
            'data' => [
                'access_token' => $payload['access_token'],
                'refresh_token' => $payload['refresh_token'],
                'provider' => $payload['provider'],
                'scopes' => $payload['scopes'],
                'email' => $payload['email'],
                'account_id' => $payload['account_id'],

        return JWT::encode($jwtPayload, $config['key'], $this->alg);

And save everything in the database for later use.

Schema::create('oauth2_accounts', function (Blueprint $table) {
    $table->string('account_id', 100);

    public function saveFromUser(User $user, string $provider)
        $payload = [
            'account_id' => $user->getId(),
            'email' => $user->getEmail(),
            'name' => $user->getName(),
            'picture' => $user->getPicture(),
            'provider' => $provider,
            'access_token' => $user->getAccessToken(),
            'refresh_token' => $user->getRefreshToken(),
            'scopes' => implode(' ', $user->getScopes()),
            'expires_at' => $user->getExpiresAt(),
            'created_at' => now(),
            'updated_at' => now()

        $payload['token'] = $this->encrypter->encode($payload);

        unset($payload['access_token'], $payload['refresh_token'], $payload['scopes']);

        if (DB::table('oauth2_accounts')
            ->where('account_id', $payload['account_id'])
            ->where('provider', $provider)
        ) {

                ->where('account_id', $payload['account_id'])
                ->where('provider', $provider)
        } else {

OAuth Accounts table

Bottom Line

In this article we created the application and the driver to work with the Google API, as well as authorized the user through OAuth 2 from Google and got tokens to access the server. We will need tokens to get data at any time. This approach will allow us to use access to API in any application, be it WEB or mobile.

You can find the full source code in the Laravel package at the GitHub.

Related links

Similar Articles