Google OAuth with Lucky
Tags: crystal, lucky
Published: 2018-11-02 16:48:15

---
title: Google OAuth with Lucky
published: true
description: How to add Google OAuth to a Lucky application
tags: crystal, lucky
---

This tutorial will show you how to obtain an access token for a user through their google account. You can use this for doing things like connecting to google docs, drive, analytics, and a ton of other things. Maybe you want to make a form that stores data in google sheets. Or you might want to build your own custom dashboard that shows your site's google analytics data. Whatever the case, you'll need the access token after a user has authenticated with google.

To start this, we will just pull the master branch of [Lucky](https://luckyframework.org/) with [Crystal](https://crystal-lang.org/) 0.26.1. Since these things are changing all the time, there is a chance that by the time you read this, it may all be out of date. Sorry!

If you don't already have crystal installed, check out the [installation guide](https://crystal-lang.org/docs/installation/).

## Step 1

We need to get our lucky cli tool. 

```
git clone https://github.com/luckyframework/lucky_cli.git
cd lucky_cli
crystal build --release ./src/lucky.cr
mv ./lucky /usr/local/bin/
```

## Step 2

Now that we have a lucky cli tool, we can generate the lucky app. `cd` in to your directory of choice and then...

```
lucky init
> Project name? google_auth
> API only or full support for HTML and Webpack? (api/full): full
> Generate authentication? (y/n): n
cd google_auth
./bin/setup
lucky dev
```

Woot! If the browser window opened up to localhost:3001 with a Lucky Welcome Page, then you're all good. If not, come have a [chat](https://gitter.im/luckyframework/Lobby) with us so we can figure out what's wrong.

## Step 3

Ok, now that you have a running app, we have a baseline just in case something doesn't work from this point on. Feel free to `git` this thing up with a branch or something.

We need to add a new shard to help us do most of the heavy lifting. Add in the [multi_auth](https://github.com/msa7/multi_auth) shard to your `shard.yml` file.

```yaml
dependencies:
#...
  multi_auth:
    github: msa7/multi_auth
    branch: master
# ...
```

> NOTE: At the time I originally wrote this stuff, I found a [bug](https://github.com/msa7/multi_auth/pull/12). If this PR isn't merged, you may need to use [my fork](https://github.com/jwoertink/multi_auth/tree/bugs/missing_hash_key)

Now that you have that added, be sure to run `shards install` to install that shard.

## Step 4

Time to add some code! Create this file in your `config/` directory

```crystal
# config/multi_auth_handler.cr
require "multi_auth"

class MultiAuthHandler
  MultiAuth.config("google", ENV["GOOGLE_OAUTH_ID"], ENV["GOOGLE_OAUTH_SECRET"])

  def self.authorize_uri(provider : String)
    MultiAuth.make(provider, "#{Lucky::RouteHelper.settings.base_uri}/oauth/#{provider}/callback").authorize_uri(scope: "profile")
  end

  def self.user(provider : String, params : Enumerable({String, String}))
    MultiAuth.make(provider, "#{Lucky::RouteHelper.settings.base_uri}/oauth/#{provider}/callback").user(params)
  end
end
```

This class sets up your MultiAuth config, and then gives you 2 convenience methods. The first `authorize_uri` generates the URL your user is redirected to which in this case is google. The second `user` is the OAuth'd user that google returns.

Next up let's make some pages.

Edit the `src/actions/home/index.cr` file.
```crystal
class Home::Index < BrowserAction
  get "/" do
    token = session.get?(:token) || ""    # add this line
    render Home::IndexPage, token: token  # update this line
  end
end
```
This just tells Lucky to use your custom IndexPage instead of the default one.


Create a `src/pages/home/index_page.cr` file.
```crystal
class Home::IndexPage < MainLayout
  needs token : String

  def content
    h1 @token
    div do
      link "Login", to: OAuth::Handler.with("google")
    end
  end
end
```
This is your shiny new home page with a link to login.


Create a `src/actions/oauth/handler.cr` file.
```crystal
class OAuth::Handler < BrowserAction
  get "/oauth/:provider" do
    redirect to: MultiAuthHandler.authorize_uri(provider)
  end
end
```
This action redirects the user to google to select their google account.

Create a `src/actions/oauth/callback.cr` file.
```crystal
class OAuth::Callback < BrowserAction
  get "/oauth/:provider/callback" do
    user = MultiAuthHandler.user(provider, request.query_params)
    if user.access_token
      session.set(:token, user.access_token.as(OAuth2::AccessToken).access_token)
      flash.success = "It worked!"
    else
      flash.failure = "That did not work"
    end
    redirect to: Home::Index
  end
end
```
This action is hit when the user leaves google and comes back to your lucky app. We pass in the params that google sends to us, and then get the user info. If the user has an `access_token`, then we set that token in to our user session. The `flash` will be the message displayed to the user if this worked or not. (Hopefully it worked). Finally we just redirect to the home page.

That's actually all of the code we need to do this. There's a few bits we're missing though. So if you tried to run the code at this point, you'll probably see some exceptions blowing up. 

## Step 5

We're just running this all in development, but for development, google won't let you redirect to localhost. That's fine though, we can easily get around that. We're going to "pretend" our app domain is actually `http://google-auth.lvh.me:3001`. `lvh.me` is a special domain setup to point back to your local computer. The nice thing is it looks like a real domain, gives you the ability to use a subdomain, and you can still access your dev site with no other configurations. Though, we do need to change 1 thing.

Open up `config/watch.yml` and update to this
```yaml
host: google-auth.lvh.me
port: 3001
```

Back in the first file we added, there was a line of code `Lucky::RouteHelper.settings.base_uri`. This points back to this watch file in local development. By default it would be `0.0.0.0` which is usually fine; however, in this case we're building a URL to tell google to send us back to. We want google to send us back to `http://google-auth.lvh.me:3001/oauth/google/callback`. If we don't update this watch.yml, google would try to send us back to `http://0.0.0.0:5000/oauth/google/callback`, and google will yell at you.

Lastly, there was mention of 2 environment variables `ENV["GOOGLE_OAUTH_ID"]`, `ENV["GOOGLE_OAUTH_SECRET"]`. We need to actually obtain these. I'd say this is probably the most difficult part of the whole thing. 

1. Register your app - https://console.developers.google.com/
2. Generate OAuth credentials - https://console.developers.google.com/apis/credentials
3. Enable Google People API - https://console.developers.google.com/apis/api/people.googleapis.com/overview

If you get stuck on these steps, there's tons of different links. I suggest google search for "google oauth credentials". 

Once you have your "OAuth 2.0 client IDs" setup under Credentials, you'll need to update the *Authorized redirect URIs* section and add in `http://google-auth.lvh.me:3001`. That will allow google to safely redirect you back to your lucky app. 

If you have your oauth keys at this point, head back to your lucky code, and add a new `.env` file in the root of your project.

```
GOOGLE_OAUTH_ID=your_oauth_client_id
GOOGLE_OAUTH_SECRET=your_oauth_client_secret
```

That should be it! Make sure your app is booted, go to the home page, and click on "Login". You'll be taken to google to select your account and agree that "Google auth app wants to look at your profile data". After you accept, you will be redirected back to the home page of your lucky app with a giant h1 tag showing your access token.

Now that you understand the basics, it's time to build your real app, and actually make it pretty and functional!