跳至主要内容

使用 GitHub 应用构建“使用 GitHub 登录”按钮

请遵循本教程编写 Ruby 代码,以通过 Web 应用流程为您的 GitHub 应用生成用户访问令牌。

简介

本教程演示如何为网站构建“使用 GitHub 登录”按钮。该网站将使用 GitHub 应用通过 Web 应用流程生成用户访问令牌。然后,网站使用用户访问令牌代表已认证的用户发出 API 请求。

本教程使用 Ruby,但您可以将 Web 应用流程与用于 Web 开发的任何编程语言一起使用。

关于 Web 应用流程和用户访问令牌

如果要将应用的操作归因于用户,则您的应用应使用用户访问令牌。有关更多信息,请参阅“代表用户使用 GitHub 应用进行身份验证”。

有两种方法可以为 GitHub 应用生成用户访问令牌:Web 应用流程和设备流程。如果您的应用可以访问 Web 界面,则应使用 Web 应用流程。如果您的应用无法访问 Web 界面,则应改用设备流程。有关更多信息,请参阅“为 GitHub 应用生成用户访问令牌”和“使用 GitHub 应用构建 CLI”。

先决条件

本教程假设您已注册了一个 GitHub 应用。有关注册 GitHub 应用的更多信息,请参阅“注册 GitHub 应用”。

在遵循本教程之前,您必须为您的应用设置回调 URL。本教程使用带有默认 URL http://localhost:4567 的本地 Sinatra 服务器。例如,要使用本地 Sinatra 应用的默认 URL,您的回调 URL 可以是 http://localhost:4567/github/callback。准备好部署应用后,您可以更改回调 URL 以使用您的实时服务器地址。有关更新应用回调 URL 的更多信息,请参阅“修改 GitHub 应用注册”和“关于用户授权回调 URL”。

本教程假设您对 Ruby 和 Ruby 模板系统 ERB 有基本的了解。有关更多信息,请参阅RubyERB

安装依赖项

本教程使用 Ruby gem Sinatra 来创建 Ruby Web 应用。有关更多信息,请参阅Sinatra 自述文件

本教程使用 Ruby gem dotenv 来访问存储在 .env 文件中的值。有关更多信息,请参阅dotenv 自述文件

要遵循本教程,您必须在 Ruby 项目中安装 Sinatra 和 dotenv gem。例如,您可以使用Bundler来完成此操作。

  1. 如果您尚未安装 Bundler,请在终端中运行以下命令

    gem install bundler
    
  2. 如果您尚未为您的应用创建 Gemfile,请在终端中运行以下命令

    bundle init
    
  3. 如果您尚未为您的应用创建 Gemfile.lock,请在终端中运行以下命令

    bundle install
    
  4. 通过在终端中运行以下命令安装 gem

    bundle add sinatra
    
    bundle add dotenv
    

存储客户端 ID 和客户端密钥

本教程将向您展示如何将客户端 ID 和客户端密钥存储在环境变量中,并使用 ENV.fetch 访问它们。部署应用时,您需要更改存储客户端 ID 和客户端密钥的方式。有关更多信息,请参阅“安全存储您的客户端密钥”。

  1. 在 GitHub 上任何页面右上角,点击您的个人资料照片。

  2. 导航到您的帐户设置。

    • 对于个人帐户拥有的应用,请点击“**设置**”。
    • 对于组织拥有的应用
      1. 点击“**您的组织**”。
      2. 在组织右侧,点击“**设置**”。
  3. 在左侧边栏中,点击“** 开发者设置**”。

  4. 在左侧边栏中,点击“**GitHub 应用**”。

  5. 在要使用的 GitHub 应用旁边,点击“**编辑**”。

  6. 在应用的设置页面上,查找应用的客户端 ID。您将在后续步骤中将其添加到 .env 文件中。请注意,客户端 ID 与应用 ID 不同。

  7. 在应用的设置页面上,点击“**生成新的客户端密钥**”。您将在后续步骤中将客户端密钥添加到 .env 文件中。

  8. 在与 Gemfile 相同的级别创建一个名为 .env 的文件。

  9. 如果您的项目还没有 .gitignore 文件,请在与 Gemfile 相同的级别创建一个 .gitignore 文件。

  10. .env 添加到您的 .gitignore 文件中。这将防止您意外提交客户端密钥。有关 .gitignore 文件的更多信息,请参阅“忽略文件”。

  11. 将以下内容添加到您的 .env 文件中。将 YOUR_CLIENT_ID 替换为您的应用的客户端 ID。将 YOUR_CLIENT_SECRET 替换为您的应用的客户端密钥。

    CLIENT_ID="YOUR_CLIENT_ID"
    CLIENT_SECRET="YOUR_CLIENT_SECRET"
    

添加生成用户访问令牌的代码

要获取用户访问令牌,您首先需要提示用户授权您的应用。当用户授权您的应用时,他们会被重定向到您的应用的回调 URL。对回调 URL 的请求包含一个 code 查询参数。当您的应用收到请求以服务该回调 URL 时,您可以将 code 参数交换为用户访问令牌。

这些步骤将引导您编写生成用户访问令牌的代码。要跳至最终代码,请参阅“完整代码示例”。

  1. 在与您的 .env 文件相同的目录中,创建一个 Ruby 文件以保存将生成用户访问令牌的代码。本教程将该文件命名为 app.rb

  2. app.rb 的顶部,添加这些依赖项

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

    sinatradotenv/load 依赖项使用您之前安装的 gem。net/httpjson 是 Ruby 标准库的一部分。

  3. 将以下代码添加到 app.rb 中,以从您的 .env 文件中获取应用的客户端 ID 和客户端密钥。

    Ruby
    CLIENT_ID = ENV.fetch("CLIENT_ID")
    CLIENT_SECRET = ENV.fetch("CLIENT_SECRET")
    
  4. 将以下代码添加到 app.rb 中,以显示一个链接,该链接将提示用户对您的应用进行身份验证。

    Ruby
    get "/" do
      link = '<a href="https://github.com/login/oauth/authorize?client_id=<%= CLIENT_ID %>">Login with GitHub</a>'
      erb link
    end
    
  5. 将以下代码添加到 app.rb 中,以处理对应用回调 URL 的请求并从请求中获取 code 参数。将 CALLBACK_URL 替换为应用的回调 URL,减去域名。例如,如果您的回调 URL 是 http://localhost:4567/github/callback,则将 CALLBACK_URL 替换为 /github/callback

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

    目前,代码只是呈现一条消息以及 code 参数。以下步骤将扩展此代码块。

  6. 可选:检查您的进度

    app.rb 现在看起来像这样,其中 CALLBACK_URL 是应用的回调 URL,减去域名

    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. 在您的终端中,从存储 app.rb 的目录中运行 ruby app.rb。本地 Sinatra 服务器应该启动。

    2. 在您的浏览器中,导航到 http://localhost:4567。您应该会看到一个文本为“使用 GitHub 登录”的链接。

    3. 点击“使用 GitHub 登录”链接。

      如果您尚未授权应用,则点击该链接应该会将您带到 https://github.com/login/oauth/authorize?client_id=CLIENT_ID,其中 CLIENT_ID 是您的应用的客户端 ID。这是一个 GitHub 页面,提示用户授权您的应用。如果您点击按钮授权您的应用,您将转到您的应用的回调 URL。

      如果您之前授权过该应用并且授权尚未撤销,则您将跳过授权提示并直接转到回调 URL。如果您想查看授权提示,可以撤销以前的授权。有关更多信息,请参阅“查看和撤销 GitHub 应用的授权”。

    4. 通过点击“使用 GitHub 登录”链接,然后在提示时授权应用,访问的回调 URL 页面,应显示类似于“已成功授权!获取代码 agc622abb6135be5d1f2”的文本。

    5. 在运行 Sinatra 的终端中,通过输入 Ctrl+C 停止服务器。

  7. app.rb 的内容替换为以下代码,其中 CALLBACK_URL 是应用的回调 URL,减去域名。

    此代码添加了将 code 参数交换为用户访问令牌的逻辑

    • parse_response 函数解析来自 GitHub API 的响应。
    • exchange_code 函数将 code 参数交换为用户访问令牌。
    • 回调 URL 请求的处理程序现在调用 exchange_code 将代码参数交换为用户访问令牌。

    • 回调页面现在显示文本以指示已生成令牌。如果令牌生成不成功,页面将指示失败。
    Ruby
    require "sinatra"
    require "dotenv/load"
    require "net/http"
    require "json"
    
    CLIENT_ID = ENV.fetch("CLIENT_ID")
    CLIENT_SECRET = ENV.fetch("CLIENT_SECRET")
    
    def parse_response(response)
      case response
      when Net::HTTPOK
        JSON.parse(response.body)
      else
        puts response
        puts response.body
        {}
      end
    end
    
    def exchange_code(code)
      params = {
        "client_id" => CLIENT_ID,
        "client_secret" => CLIENT_SECRET,
        "code" => code
      }
      result = Net::HTTP.post(
        URI("https://github.com/login/oauth/access_token"),
        URI.encode_www_form(params),
        {"Accept" => "application/json"}
      )
    
      parse_response(result)
    end
    
    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"]
    
      token_data = exchange_code(code)
    
      if token_data.key?("access_token")
        token = token_data["access_token"]
    
        render = "Successfully authorized! Got code #{code} and exchanged it for a user access token ending in #{token[-9..-1]}."
        erb render
      else
        render = "Authorized, but unable to exchange code #{code} for token."
        erb render
      end
    end
    
  8. 可选:检查您的进度

    1. 在您的终端中,从存储 app.rb 的目录中运行 ruby app.rb。本地 Sinatra 服务器应该启动。
    2. 在您的浏览器中,导航到 http://localhost:4567。您应该会看到一个文本为“使用 GitHub 登录”的链接。
    3. 点击“使用 GitHub 登录”链接。
    4. 如果系统提示,请授权您的应用。
    5. 回调 URL 页面(通过点击“使用 GitHub 登录”链接并在系统提示时授权应用访问)应显示类似于“已成功授权!获取代码 4acd44861aeda86dacce 并将其交换为以 2zU5kQziE 结尾的用户访问令牌。”的文本。
    6. 在运行 Sinatra 的终端中,通过输入 Ctrl+C 停止服务器。
  9. 现在您已拥有用户访问令牌,您可以使用该令牌代表用户发出 API 请求。例如

    将此函数添加到 app.rb 中,以使用 /user REST API 端点获取有关用户的信息

    Ruby
    def user_info(token)
      uri = URI("https://api.github.com/user")
    
      result = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
        body = {"access_token" => token}.to_json
    
        auth = "Bearer #{token}"
        headers = {"Accept" => "application/json", "Content-Type" => "application/json", "Authorization" => auth}
    
        http.send_request("GET", uri.path, body, headers)
      end
    
      parse_response(result)
    end
    

    更新回调处理程序以调用 user_info 函数并显示用户的姓名和 GitHub 登录名。请记住将 CALLBACK_URL 替换为您的应用的回调 URL,减去域名。

    Ruby
    get "CALLBACK_URL" do
      code = params["code"]
    
      token_data = exchange_code(code)
    
      if token_data.key?("access_token")
        token = token_data["access_token"]
    
        user_info = user_info(token)
        handle = user_info["login"]
        name = user_info["name"]
    
        render = "Successfully authorized! Welcome, #{name} (#{handle})."
        erb render
      else
        render = "Authorized, but unable to exchange code #{code} for token."
        erb render
      end
    end
    
  10. 检查您的代码是否与下一部分中的完整代码示例一致。您可以按照完整代码示例下方“测试”部分中概述的步骤测试您的代码。

完整代码示例

这是上一节中概述的完整代码示例。

CALLBACK_URL 替换为您的应用的回调 URL,减去域名。例如,如果您的回调 URL 是 http://localhost:4567/github/callback,则将 CALLBACK_URL 替换为 /github/callback

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

CLIENT_ID = ENV.fetch("CLIENT_ID")
CLIENT_SECRET = ENV.fetch("CLIENT_SECRET")

def parse_response(response)
  case response
  when Net::HTTPOK
    JSON.parse(response.body)
  else
    puts response
    puts response.body
    {}
  end
end

def exchange_code(code)
  params = {
    "client_id" => CLIENT_ID,
    "client_secret" => CLIENT_SECRET,
    "code" => code
  }
  result = Net::HTTP.post(
    URI("https://github.com/login/oauth/access_token"),
    URI.encode_www_form(params),
    {"Accept" => "application/json"}
  )

  parse_response(result)
end

def user_info(token)
  uri = URI("https://api.github.com/user")

  result = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
    body = {"access_token" => token}.to_json

    auth = "Bearer #{token}"
    headers = {"Accept" => "application/json", "Content-Type" => "application/json", "Authorization" => auth}

    http.send_request("GET", uri.path, body, headers)
  end

  parse_response(result)
end

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"]

  token_data = exchange_code(code)

  if token_data.key?("access_token")
    token = token_data["access_token"]

    user_info = user_info(token)
    handle = user_info["login"]
    name = user_info["name"]

    render = "Successfully authorized! Welcome, #{name} (#{handle})."
    erb render
  else
    render = "Authorized, but unable to exchange code #{code} for token."
    erb render
  end
end

测试

本教程假设您的应用代码存储在名为 app.rb 的文件中,并且您正在使用本地 Sinatra 应用的默认 URL http://localhost:4567

  1. 在您的终端中,从存储 app.rb 的目录中运行 ruby app.rb。本地 Sinatra 服务器应该启动。

  2. 在您的浏览器中,导航到 http://localhost:4567。您应该会看到一个文本为“使用 GitHub 登录”的链接。

  3. 点击“使用 GitHub 登录”链接。

    如果您尚未授权应用,则点击该链接应该会将您带到 https://github.com/login/oauth/authorize?client_id=CLIENT_ID,其中 CLIENT_ID 是您的应用的客户端 ID。这是一个 GitHub 页面,提示用户授权您的应用。如果您点击按钮授权您的应用,您将转到您的应用的回调 URL。

    如果您之前授权过该应用并且授权尚未撤销,则您将跳过授权提示并直接转到回调 URL。如果您想查看授权提示,可以撤销以前的授权。有关更多信息,请参阅“查看和撤销 GitHub 应用的授权”。

  4. 回调 URL 页面(通过点击“使用 GitHub 登录”链接并在系统提示时授权应用访问)应显示类似于“已成功授权!欢迎,Mona Lisa (octocat)。 ”的文本。

  5. 在运行 Sinatra 的终端中,通过输入 Ctrl+C 停止服务器。

后续步骤

安全存储您的客户端密钥

您永远不应该公开您的应用的客户端密钥。本教程将客户端密钥存储在 .gitignore 文件 .env 中,并使用 ENV.fetch 访问其值。部署应用时,您应该选择一种安全的方式来存储客户端密钥,并更新您的代码以相应地获取该值。

例如,您可以将密钥存储在部署应用程序的服务器上的环境变量中。您还可以使用 Azure Key Vault 等密钥管理服务。

更新部署的回调 URL

本教程使用了以 http://localhost:4567 开头的回调 URL。但是,http://localhost:4567 仅在您启动 Sinatra 服务器时在本地对您的计算机可用。在部署应用之前,您应该更新回调 URL 以使用您在生产环境中使用的回调 URL。有关更新应用回调 URL 的更多信息,请参阅“修改 GitHub 应用注册”和“关于用户授权回调 URL”。

处理多个回调 URL

本教程使用了单个回调 URL,但您的应用最多可以有 10 个回调 URL。如果您想使用多个回调 URL

  • 将其他回调 URL 添加到您的应用中。有关添加回调 URL 的更多信息,请参阅“修改 GitHub 应用注册”。
  • 当您链接到 https://github.com/login/oauth/authorize 时,使用 redirect_uri 查询参数将用户重定向到所需的回调 URL。有关更多信息,请参阅“为 GitHub 应用生成用户访问令牌”。
  • 在您的应用代码中,处理每个回调 URL,类似于从 get "CALLBACK_URL" do 开始的代码块。

指定其他参数

当您链接到 https://github.com/login/oauth/authorize 时,您可以传递其他查询参数。有关更多信息,请参阅“为 GitHub 应用生成用户访问令牌”。

与传统的 OAuth 令牌不同,用户访问令牌不使用范围,因此您无法通过 scope 参数指定范围。相反,它使用细粒度的权限。用户访问令牌仅具有用户和应用都具有的权限。

调整代码以满足您的应用需求

本教程演示了如何显示有关已认证用户的的信息,但您可以调整此代码以执行其他操作。请记住,如果您的应用需要您想要发出的 API 请求的其他权限,请更新您的应用权限。有关更多信息,请参阅“为 GitHub 应用选择权限”。

本教程将所有代码存储在一个文件中,但您可能希望将函数和组件移动到单独的文件中。

安全存储令牌

本教程生成用户访问令牌。除非您选择不使用户访问令牌过期,否则用户访问令牌将在八小时后过期。您还将收到一个刷新令牌,该令牌可以重新生成用户访问令牌。有关更多信息,请参阅“刷新用户访问令牌”。

如果您计划进一步与 GitHub 的 API 交互,则应将令牌存储以备将来使用。如果您选择存储用户访问令牌或刷新令牌,则必须安全地存储它。您永远不应该公开令牌。

有关更多信息,请参阅“创建 GitHub 应用的最佳实践”。

遵循最佳实践

您应该努力遵循 GitHub 应用的最佳实践。有关更多信息,请参阅“创建 GitHub 应用的最佳实践”。