DEV Community

Jeremy Woertink
Jeremy Woertink

Posted on

Google OAuth with 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 with Crystal 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.

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/
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 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 shard to your shard.yml file.

dependencies:
#...
  multi_auth:
    github: msa7/multi_auth
    branch: master
# ...
Enter fullscreen mode Exit fullscreen mode

NOTE: At the time I originally wrote this stuff, I found a bug. If this PR isn't merged, you may need to use my fork

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

# 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
Enter fullscreen mode Exit fullscreen mode

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.

class Home::Index < BrowserAction
  get "/" do
    token = session.get?(:token) || ""    # add this line
    render Home::IndexPage, token: token  # update this line
  end
end
Enter fullscreen mode Exit fullscreen mode

This just tells Lucky to use your custom IndexPage instead of the default one.

Create a src/pages/home/index_page.cr file.

class Home::IndexPage < MainLayout
  needs token : String

  def content
    h1 @token
    div do
      link "Login", to: OAuth::Handler.with("google")
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

This is your shiny new home page with a link to login.

Create a src/actions/oauth/handler.cr file.

class OAuth::Handler < BrowserAction
  get "/oauth/:provider" do
    redirect to: MultiAuthHandler.authorize_uri(provider)
  end
end
Enter fullscreen mode Exit fullscreen mode

This action redirects the user to google to select their google account.

Create a src/actions/oauth/callback.cr file.

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
Enter fullscreen mode Exit fullscreen mode

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

host: google-auth.lvh.me
port: 3001
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (1)

Collapse
 
konung profile image
konung

Excellent write up!