Skip to main content

Building a "Login with GitHub" button with a GitHub App

Follow this tutorial to write Ruby code to generate a user access token via the web application flow for your GitHub App.

Introduction

This tutorial demonstrates how to build a "Login with GitHub" button for a website. The website will use a GitHub App to generate a user access token via the web application flow. Then, the website uses the user access token to make API requests on behalf of the authenticated user.

This tutorial uses Ruby, but you can use the web application flow with any programming language that is used for web development.

About web application flow and user access tokens

Your app should use a user access token if you want to attribute the app's actions to a user. For more information, see Authenticating with a GitHub App on behalf of a user.

There are two ways to generate a user access token for a GitHub App: web application flow and device flow. If your app has access to a web interface, you should use web application flow. If your app does not have access to a web interface, you should use device flow instead. For more information, see Generating a user access token for a GitHub App and Building a CLI with a GitHub App.

Prerequisites

This tutorial assumes that you have already registered a GitHub App. For more information about registering a GitHub App, see Registering a GitHub App.

Before following this tutorial, you must set a callback URL for your app. This tutorial uses a local Sinatra server with the default URL of http://localhost:4567. For example, to work with the default URL for a local Sinatra application, your callback URL can be http://localhost:4567/github/callback. Once you are ready to deploy your app, you can change the callback URL to use your live server address. For more information about updating the callback URL for your app, see Modifying a GitHub App registration and About the user authorization callback URL.

This tutorial assumes that you have a basic understanding of Ruby and of the Ruby template system, ERB. For more information, see Ruby and ERB.

Install dependencies

This tutorial uses the Ruby gem, Sinatra, to create a web application with Ruby. For more information, see the Sinatra README.

This tutorial uses the Ruby gem, dotenv, to access values stored in a .env file. For more information, see the dotenv README.

To follow this tutorial, you must install the Sinatra and dotenv gems in your Ruby project. For example, you can do this with Bundler:

  1. If you don't already have Bundler installed, run the following command in your terminal:

    gem install bundler
    
  2. If you don't already have a Gemfile for your app, run the following command in your terminal:

    bundle init
    
  3. If you don't already have a Gemfile.lock for your app, run the following command in your terminal:

    bundle install
    
  4. Install the gems by running the following commands in your terminal:

    bundle add sinatra
    
    bundle add dotenv
    

Store the client ID and client secret

This tutorial will show you how to store the client ID and client secret in environment variables and access them with ENV.fetch. When you deploy your app, you will want to change how you store the client ID and client secret. For more information, see Securely store your client secret.

  1. In the upper-right corner of any page on GitHub, click your profile picture.

  2. Navigate to your account settings.

    • For an app owned by a personal account, click Settings.
    • For an app owned by an organization:
      1. Click Your organizations.
      2. To the right of the organization, click Settings.
  3. In the left sidebar, click Developer settings.

  4. In the left sidebar, click GitHub Apps.

  5. Next to the GitHub App that you want to work with, click Edit.

  6. On the app's settings page, find the client ID for your app. You will add it to a .env file in a following step. Note that the client ID is different from the app ID.

  7. On the app's settings page, click Generate a new client secret. You will add the client secret to a .env file in a following step.

  8. Create a file called .env at the same level as your Gemfile.

  9. If your project doesn't already have a .gitignore file, create a .gitignore file at the same level as your Gemfile.

  10. Add .env to your .gitignore file. This will prevent you from accidentally committing your client secret. For more information about .gitignore files, see Ignoring files.

  11. Add the following contents to your .env file. Replace YOUR_CLIENT_ID with the client ID of your app. Replace YOUR_CLIENT_SECRET with the client secret for your app.

    CLIENT_ID="YOUR_CLIENT_ID"
    CLIENT_SECRET="YOUR_CLIENT_SECRET"
    

Add code to generate a user access token

To get a user access token, you first need to prompt the user to authorize your app. When a user authorizes your app, they are redirected to the callback URL for your app. The request to the callback URL includes a code query parameter. When your app gets a request to serve that callback URL, you can exchange the code parameter for a user access token.

These steps lead you through writing code to generate a user access token. To skip ahead to the final code, see Full code example.

  1. In the same directory as your .env file, create a Ruby file to hold the code that will generate a user access token. This tutorial will name the file app.rb.

  2. At the top of app.rb, add these dependencies:

    Ruby
    require "sinatra"
    require "dotenv/load"
    require "net/http"
    require "json"
    

    The sinatra and dotenv/load dependencies use the gems that you installed earlier. net/http and json are part of the Ruby standard library.

  3. Add the following code to app.rb, to get your app's client ID and client secret from your .env file.

    Ruby
    CLIENT_ID = ENV.fetch("CLIENT_ID")
    CLIENT_SECRET = ENV.fetch("CLIENT_SECRET")
    
  4. Add the following code to app.rb to display a link that will prompt users to authenticate your app.

    Ruby
    get "/" do
      link = '<a href="https://github.com/login/oauth/authorize?client_id=<%= CLIENT_ID %>">Login with GitHub</a>'
      erb link
    end
    
  5. Add the following code to app.rb to handle requests to your app's callback URL and get the code parameter from the request. Replace CALLBACK_URL with the callback URL for your app, minus the domain. For example, if your callback URL is http://localhost/github/callback, replace CALLBACK_URL with /github/callback.

    Ruby
    get "CALLBACK_URL" do
      code = params["code"]
      render = "Successfully authorized! Got code #{code}."
      erb render
    end
    

    Currently, the code just renders a message along with the code parameter. The following steps will expand this code block.

  6. Optionally, check your progress:

    app.rb now looks like this, where CALLBACK_URL is the callback URL for your app, minus the domain:

    Ruby
    require "sinatra"
    require "dotenv/load"
    require "net/http"
    require "json"
    
    CLIENT_ID = ENV.fetch("CLIENT_ID")
    CLIENT_SECRET = ENV.fetch("CLIENT_SECRET")
    
    get "/" do
      link = '<a href="https://github.com/login/oauth/authorize?client_id=<%= CLIENT_ID %>">Login with GitHub</a>'
      erb link
    end
    
    get "CALLBACK_URL" do
      code = params["code"]
      render = "Successfully authorized! Got code #{code}."
      erb render
    end
    
    1. In your terminal, from the directory where app.rb is stored, run ruby app.rb. A local Sinatra server should start.

    2. In your browser, navigate to http://localhost:4567. You should see a link with the text "Login with GitHub".

    3. Click on the "Login with GitHub" link.

      If you have not authorized the app, clicking on the link should take you to https://github.com/login/oauth/authorize?client_id=CLIENT_ID, where CLIENT_ID is the client ID of your app. This is a GitHub page that prompts users to authorize your app. If you click the button to authorize your app, you will go to the callback URL for your app.

      If you previously authorized your app and the authorization has not been revoked, you will skip the authorization prompt and go directly to the callback URL instead. You can revoke your previous authorization if you want to see the authorization prompt. For more information, see Reviewing and revoking authorization of GitHub Apps.

    4. The callback URL page, reached by clicking the "Login with GitHub" link and then authorizing the app if prompted to do so, should display the text similar to "Successfully authorized! Got code agc622abb6135be5d1f2."

    5. In your terminal where Sinatra is running, stop the server by entering Ctrl+C.

  7. Replace the content of app.rb with the following code, where CALLBACK_URL is the callback URL for your app, minus the domain.

    This code adds logic to exchange the code parameter for a user access token:

    • The parse_response function parses the response from the GitHub API.
    • The exchange_code function exchanges the code parameter for a user access token.
    • The handler for the callback URL request now calls exchange_code to exchange the code parameter for a user access token.
    • The callback page now shows text to indicate that a token was generated. If the token generation was not successful, the page will indicate that failure.
    Ruby