Skip to main content

GitHub 앱을 사용하여 CLI 빌드

이 자습서에 따라 Ruby에서 디바이스 흐름을 통해 GitHub App에 대한 사용자 액세스 토큰을 생성하는 CLI를 빌드합니다.

소개

이 자습서에서는 GitHub App 기반의 CLI(명령줄 인터페이스)를 빌드하는 방법과 디바이스 흐름을 사용하여 앱에 대한 사용자 액세스 토큰을 생성하는 방법을 보여 줍니다.

이 CLI에는 다음 세 가지 명령이 포함됩니다.

  • help: 사용 지침을 출력합니다.
  • login: 앱이 사용자를 대신하여 API 요청을 하는 데 사용할 수 있는 사용자 액세스 토큰을 생성합니다.
  • whoami: 로그인한 사용자에 대한 정보를 반환합니다.

이 자습서에서는 Ruby를 사용하지만, CLI를 작성하고 디바이스 흐름을 사용하여 프로그래밍 언어로 사용자 액세스 토큰을 생성할 수도 있습니다.

디바이스 흐름 및 사용자 액세스 토큰 정보

CLI는 디바이스 흐름을 사용하여 사용자를 인증하고 사용자 액세스 토큰을 생성합니다. 그런 다음 CLI는 사용자 액세스 토큰을 사용하여 인증된 사용자를 대신하여 API 요청을 수행할 수 있습니다.

앱의 작업을 사용자에게 귀속하려면 앱에서 사용자 액세스 토큰을 사용해야 합니다. 자세한 내용은 "사용자를 대신하여 GitHub 앱으로 인증"을(를) 참조하세요.

GitHub App에 대한 사용자 액세스 토큰을 생성하는 방법에는 웹 애플리케이션 흐름과 디바이스 흐름의 두 가지가 있습니다. 앱이 비입력 시스템이거나 웹 인터페이스에 대한 액세스 권한이 없는 경우 디바이스 흐름을 사용하여 사용자 액세스 토큰을 생성해야 합니다. 예를 들어 CLI 도구, 간단한 라즈베리 파이 및 데스크톱 애플리케이션은 디바이스 흐름을 사용해야 합니다. 앱에 웹 인터페이스에 대한 액세스 권한이 있는 경우 웹 애플리케이션 흐름을 대신 사용해야 합니다. 자세한 내용은 "GitHub 앱에 대한 사용자 액세스 토큰 생성" 및 "GitHub 앱을 사용하여 "GitHub로 로그인" 단추 빌드"을(를) 참조하세요.

필수 조건

이 자습서에서는 GitHub App을(를) 이미 등록했다고 가정합니다. GitHub App 등록에 대한 자세한 내용은 "GitHub 앱 등록"을(를) 참조하세요.

이 자습서에 따라 진행하기 전에 먼저 앱에 대해 디바이스 흐름을 활성화해야 합니다. 앱에 대해 디바이스 흐름을 활성화하는 방법에 대한 자세한 내용은 "GitHub 앱 등록 수정"을(를) 참조하세요.

이 자습서에서는 Ruby에 대한 기본적인 이해를 갖추고 있다고 가정합니다. 자세한 내용은 Ruby를 참조하세요.

클라이언트 ID 가져오기

디바이스 흐름을 통해 사용자 액세스 토큰을 생성하려면 앱의 클라이언트 ID가 필요합니다.

  1. GitHub Enterprise Cloud의 페이지 오른쪽 위 모서리에서 프로필 사진을 클릭합니다.
  2. 계정 설정으로 이동합니다.
    • 개인 계정 소유한 앱의 경우 설정을 클릭합니다.
    • 조직이 소유한 앱의 경우:
      1. 사용자의 조직을 클릭합니다.
      2. 조직 오른쪽에서 설정을 클릭합니다.
  3. 왼쪽 사이드바에서 개발자 설정을 클릭합니다.
  4. 왼쪽 사이드바에서 GitHub Apps 을 클릭합니다.
  5. 작업하려는 GitHub App의 옆에 있는 편집을 클릭합니다.
  6. 앱의 설정 페이지에서 앱의 클라이언트 ID를 확인합니다. 이 자습서의 뒷부분에서 사용하게 됩니다. 클라이언트 ID는 앱 ID와 다릅니다.

CLI 작성

이 단계에서는 CLI를 빌드하고 디바이스 흐름을 사용하여 사용자 액세스 토큰을 가져오는 방법을 안내합니다. 최종 코드로 건너뛰려면 "전체 코드 예제"를 참조하세요.

설정

  1. 사용자 액세스 토큰을 생성하는 코드를 저장할 Ruby 파일을 만듭니다. 이 자습서에서는 app_cli.rb 파일 이름을 지정합니다.

  2. 터미널에서, app_cli.rb가 저장된 디렉터리에서 다음 명령을 실행하여 app_cli.rb 실행 파일을 만듭니다.

    Text
    chmod +x app_cli.rb
    
  3. 이 줄을 app_cli.rb의 맨 위에 추가하여 Ruby 인터프리터를 사용해 스크립트를 실행해야 함을 나타냅니다.

    Ruby
    #!/usr/bin/env ruby
    
  4. app_cli.rb의 맨 위에서 #!/usr/bin/env ruby 뒤에 다음 종속성을 추가합니다.

    Ruby
    require "net/http"
    require "json"
    require "uri"
    require "fileutils"
    

    모두 Ruby 표준 라이브러리의 일부이므로 gem을 설치할 필요가 없습니다.

  5. 진입점 역할을 할 다음 main 함수를 추가합니다. 이 함수에는 지정된 명령에 따라 다른 작업을 수행하는 case 문이 포함됩니다. 나중에 이 case 문을 확장하게 됩니다.

    Ruby
    def main
      case ARGV[0]
      when "help"
        puts "`help` is not yet defined"
      when "login"
        puts "`login` is not yet defined"
      when "whoami"
        puts "`whoami` is not yet defined"
      else
        puts "Unknown command `#{ARGV[0]}`"
      end
    end
    
  6. 파일 맨 아래에 다음 줄을 추가하여 진입점 함수를 호출합니다. 이 함수 호출은 자습서의 뒷부분에서 이 파일에 함수를 더 추가할 때 파일 맨 아래에 남아 있어야 합니다.

    Ruby
    main
    
  7. 필요에 따라 진행률을 확인합니다.

    이제 app_cli.rb는 다음과 같습니다.

    Ruby
    #!/usr/bin/env ruby
    
    require "net/http"
    require "json"
    require "uri"
    require "fileutils"
    
    def main
      case ARGV[0]
      when "help"
        puts "`help` is not yet defined"
      when "login"
        puts "`login` is not yet defined"
      when "whoami"
        puts "`whoami` is not yet defined"
      else
        puts "Unknown command `#{ARGV[0]}`"
      end
    end
    
    main
    

    터미널에서, app_cli.rb가 저장된 디렉터리에서 ./app_cli.rb help를 실행합니다. 다음 출력이 표시됩니다.

    `help` is not yet defined
    

    명령 없이 또는 처리되지 않은 명령을 사용하여 스크립트를 테스트할 수도 있습니다. 예를 들어 ./app_cli.rb create-issue는 다음을 출력합니다.

    Unknown command `create-issue`
    

help 명령 추가

  1. app_cli.rb에 다음 help 함수를 추가합니다. 현재 help 함수는 사용자에게 이 CLI가 "help" 명령 하나만 받는다는 것을 알리는 줄을 인쇄합니다. 나중에 이 help 함수를 확장하게 됩니다.

    Ruby
    def help
      puts "usage: app_cli <help>"
    end
    
  2. help 명령이 제공되면 help 함수를 호출하도록 main 함수를 업데이트합니다.

    Ruby
    def main
      case ARGV[0]
      when "help"
        help
      when "login"
        puts "`login` is not yet defined"
      when "whoami"
        puts "`whoami` is not yet defined"
      else
        puts "Unknown command #{ARGV[0]}"
      end
    end
    
  3. 필요에 따라 진행률을 확인합니다.

    이제 app_cli.rb는 다음과 같습니다. main 함수 호출이 파일의 끝에 있기만 하면 함수의 순서는 상관이 없습니다.

    Ruby
    #!/usr/bin/env ruby
    
    require "net/http"
    require "json"
    require "uri"
    require "fileutils"
    
    def help
      puts "usage: app_cli <help>"
    end
    
    def main
      case ARGV[0]
      when "help"
        help
      when "login"
        puts "`login` is not yet defined"
      when "whoami"
        puts "`whoami` is not yet defined"
      else
        puts "Unknown command #{ARGV[0]}"
      end
    end
    
    main
    

    터미널에서, app_cli.rb가 저장된 디렉터리에서 ./app_cli.rb help를 실행합니다. 다음 출력이 표시됩니다.

    usage: app_cli <help>
    

login 명령 추가

login 명령은 디바이스 흐름을 실행하여 사용자 액세스 토큰을 가져옵니다. 자세한 내용은 "GitHub 앱에 대한 사용자 액세스 토큰 생성"을(를) 참조하세요.

  1. 파일 위쪽에서 require 문 뒤에 GitHub App의 CLIENT_IDapp_cli.rb에 상수로 추가합니다. 앱의 클라이언트 ID를 찾는 방법에 대한 자세한 내용은 "클라이언트 ID 가져오기"를 참조하세요. YOUR_CLIENT_ID를 앱의 클라이언트 ID로 바꿉니다.

    Ruby
    CLIENT_ID="YOUR_CLIENT_ID"
    
  2. app_cli.rb에 다음 parse_response 함수를 추가합니다. 이 함수는 GitHub REST API의 응답을 구문 분석합니다. 응답이 200 OK 또는 201 Created 상태인 경우 이 함수는 구문 분석된 응답 본문을 반환합니다. 그렇지 않으면 이 함수는 응답을 출력하고 본문은 프로그램을 종료합니다.

    Ruby
    def parse_response(response)
      case response
      when Net::HTTPOK, Net::HTTPCreated
        JSON.parse(response.body)
      else
        puts response
        puts response.body
        exit 1
      end
    end
    
  3. app_cli.rb에 다음 request_device_code 함수를 추가합니다. 이 함수는 https://github.com/login/device/code에 대한 POST 요청을 하고 응답을 반환합니다.

    Ruby
    def request_device_code
      uri = URI("https://github.com/login/device/code")
      parameters = URI.encode_www_form("client_id" => CLIENT_ID)
      headers = {"Accept" => "application/json"}
    
      response = Net::HTTP.post(uri, parameters, headers)
      parse_response(response)
    end
    
  4. app_cli.rb에 다음 request_token 함수를 추가합니다. 이 함수는 https://github.com/login/oauth/access_token에 대한 POST 요청을 하고 응답을 반환합니다.

    Ruby
    def request_token(device_code)
      uri = URI("https://github.com/login/oauth/access_token")
      parameters = URI.encode_www_form({
        "client_id" => CLIENT_ID,
        "device_code" => device_code,
        "grant_type" => "urn:ietf:params:oauth:grant-type:device_code"
      })
      headers = {"Accept" => "application/json"}
      response = Net::HTTP.post(uri, parameters, headers)
      parse_response(response)
    end
    
  5. app_cli.rb에 다음 poll_for_token 함수를 추가합니다. 이 함수는 GitHub이(가) error 매개 변수 대신 access_token 매개 변수로 응답할 때까지 지정된 간격으로 https://github.com/login/oauth/access_token을 폴링합니다. 그런 다음 사용자 액세스 토큰을 파일에 쓰고 파일에 대한 권한을 제한합니다.

    Ruby
    def poll_for_token(device_code, interval)
    
      loop do
        response = request_token(device_code)
        error, access_token = response.values_at("error", "access_token")
    
        if error
          case error
          when "authorization_pending"
            # The user has not yet entered the code.
            # Wait, then poll again.
            sleep interval
            next
          when "slow_down"
            # The app polled too fast.
            # Wait for the interval plus 5 seconds, then poll again.
            sleep interval + 5
            next
          when "expired_token"
            # The `device_code` expired, and the process needs to restart.
            puts "The device code has expired. Please run `login` again."
            exit 1
          when "access_denied"
            # The user cancelled the process. Stop polling.
            puts "Login cancelled by user."
            exit 1
          else
            puts response
            exit 1
          end
        end
    
        File.write("./.token", access_token)
    
        # Set the file permissions so that only the file owner can read or modify the file
        FileUtils.chmod(0600, "./.token")
    
        break
      end
    end
    
  6. 다음 login 함수를 추가합니다.

    이 함수는 다음을 수행합니다.

    1. request_device_code 함수를 호출하고 응답에서 verification_uri, user_code, device_codeinterval 매개 변수를 가져옵니다.
    2. 사용자에게 이전 단계의 user_code를 입력하라는 메시지를 표시합니다.
    3. poll_for_token을 호출하여 액세스 토큰에 대한 GitHub을(를) 폴링합니다.
    4. 사용자에게 인증이 성공했음을 알립니다.
    Ruby
    def login
      verification_uri, user_code, device_code, interval = request_device_code.values_at("verification_uri", "user_code", "device_code", "interval")
    
      puts "Please visit: #{verification_uri}"
      puts "and enter code: #{user_code}"
    
      poll_for_token(device_code, interval)
    
      puts "Successfully authenticated!"
    end
    
  7. login 명령이 제공되면 login 함수를 호출하도록 main 함수를 업데이트합니다.

    Ruby
    def main
      case ARGV[0]
      when "help"
        help
      when "login"
        login
      when "whoami"
        puts "`whoami` is not yet defined"
      else
        puts "Unknown command #{ARGV[0]}"
      end
    end
    
  8. login 명령을 포함하도록 help 함수를 업데이트합니다.

    Ruby
    def help
      puts "usage: app_cli <login | help>"
    end
    
  9. 필요에 따라 진행률을 확인합니다.

    이제 app_cli.rb가 다음과 같이 표시됩니다. 여기서 YOUR_CLIENT_ID는 앱의 클라이언트 ID입니다. main 함수 호출이 파일의 끝에 있기만 하면 함수의 순서는 상관이 없습니다.

    Ruby
    #!/usr/bin/env ruby
    
    require "net/http"
    require "json"
    require "uri"
    require "fileutils"
    
    CLIENT_ID="YOUR_CLIENT_ID"
    
    def help
      puts "usage: app_cli <login | help>"
    end
    
    def main
      case ARGV[0]
      when "help"
        help
      when "login"
        login
      when "whoami"
        puts "`whoami` is not yet defined"
      else
        puts "Unknown command #{ARGV[0]}"
      end
    end
    
    def parse_response(response)
      case response
      when Net::HTTPOK, Net::HTTPCreated
        JSON.parse(response.body)
      else
        puts response
        puts response.body
        exit 1
      end
    end
    
    def request_device_code
      uri = URI("https://github.com/login/device/code")
      parameters = URI.encode_www_form("client_id" => CLIENT_ID)
      headers = {"Accept" => "application/json"}
    
      response = Net::HTTP.post(uri, parameters, headers)
      parse_response(response)
    end
    
    def request_token(device_code)
      uri = URI("https://github.com/login/oauth/access_token")
      parameters = URI.encode_www_form({
        "client_id" => CLIENT_ID,
        "device_code" => device_code,
        "grant_type" => "urn:ietf:params:oauth:grant-type:device_code"
      })
      headers = {"Accept" => "application/json"}
      response = Net::HTTP.post(uri, parameters, headers)
      parse_response(response)
    end
    
    def poll_for_token(device_code, interval)
    
      loop do
        response = request_token(device_code)
        error, access_token = response.values_at("error", "access_token")
    
        if error
          case error
          when "authorization_pending"
            # The user has not yet entered the code.
            # Wait, then poll again.
            sleep interval
            next
          when "slow_down"
            # The app polled too fast.
            # Wait for the interval plus 5 seconds, then poll again.
            sleep interval + 5
            next
          when "expired_token"
            # The `device_code` expired, and the process needs to restart.
            puts "The device code has expired. Please run `login` again."
            exit 1
          when "access_denied"
            # The user cancelled the process. Stop polling.
            puts "Login cancelled by user."
            exit 1
          else
            puts response
            exit 1
          end
        end
    
        File.write("./.token", access_token)
    
        # Set the file permissions so that only the file owner can read or modify the file
        FileUtils.chmod(0600, "./.token")
    
        break
      end
    end
    
    def login
      verification_uri, user_code, device_code, interval = request_device_code.values_at("verification_uri", "user_code", "device_code", "interval")
    
      puts "Please visit: #{verification_uri}"
      puts "and enter code: #{user_code}"
    
      poll_for_token(device_code, interval)
    
      puts "Successfully authenticated!"
    end
    
    main
    
    1. 터미널에서, app_cli.rb가 저장된 디렉터리에서 ./app_cli.rb login를 실행합니다. 다음과 비슷한 출력이 표시됩니다. 코드는 매번 다릅니다.

      Please visit: https://github.com/login/device
      and enter code: CA86-8D94
      
    2. 브라우저에서 https://github.com/login/device로 이동하고 이전 단계의 코드를 입력한 다음 계속을 클릭합니다.

    3. GitHub이(가) 앱에 권한을 부여하라는 메시지가 있는 페이지를 표시합니다. "권한 부여" 단추를 클릭합니다.

    4. 이제 터미널에 "성공적으로 인증되었습니다!"라는 메시지가 표시됩니다.

whoami 명령 추가

이제 앱이 사용자 액세스 토큰을 생성할 수 있으므로, 사용자를 대신해 API 요청을 수행할 수 있습니다. 인증된 사용자의 사용자 이름을 가져오는 whoami 명령을 추가합니다.

  1. app_cli.rb에 다음 whoami 함수를 추가합니다. 이 함수는 /user REST API 엔드포인트를 사용하여 사용자에 대한 정보를 가져옵니다. 사용자 액세스 토큰에 해당하는 사용자 이름을 출력합니다. .token 파일을 찾을 수 없으면 사용자에게 login 함수를 실행하라는 메시지가 표시됩니다.

    Ruby
    def whoami
      uri = URI("https://api.github.com/user")
    
      begin
        token = File.read("./.token").strip
      rescue Errno::ENOENT => e
        puts "You are not authorized. Run the `login` command."
        exit 1
      end
    
      response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
        body = {"access_token" => token}.to_json
        headers = {"Accept" => "application/vnd.github+json", "Authorization" => "Bearer #{token}"}
    
        http.send_request("GET", uri.path, body, headers)
      end
    
      parsed_response = parse_response(response)
      puts "You are #{parsed_response["login"]}"
    end
    
  2. 토큰이 만료되었거나 철회된 경우를 처리하도록 parse_response 함수를 업데이트합니다. 이제 401 Unauthorized 응답을 받으면 CLI에서 사용자에게 login 명령을 실행하라는 메시지를 표시합니다.

    Ruby
    def parse_response(response)
      case response
      when Net::HTTPOK, Net::HTTPCreated
        JSON.parse(response.body)
      when Net::HTTPUnauthorized
        puts "You are not authorized. Run the `login` command."
        exit 1
      else
        puts response
        puts response.body
        exit 1
      end
    end
    
  3. whoami 명령이 제공되면 whoami 함수를 호출하도록 main 함수를 업데이트합니다.

    Ruby
    def main
      case ARGV[0]
      when "help"
        help
      when "login"
        login
      when "whoami"
        whoami
      else
        puts "Unknown command #{ARGV[0]}"
      end
    end
    
  4. whoami 명령을 포함하도록 help 함수를 업데이트합니다.

    Ruby
    def help
      puts "usage: app_cli <login | whoami | help>"
    end
    
  5. 다음 섹션의 전체 코드 예제와 비교하여 코드를 확인합니다. 전체 코드 예제 아래의 "테스트" 섹션에서 설명하는 단계에 따라 코드를 테스트할 수 있습니다.

전체 코드 예제

이전 섹션에서 설명한 전체 코드 예제입니다. YOUR_CLIENT_ID를 앱의 클라이언트 ID로 바꿉니다.

Ruby
#!/usr/bin/env ruby

require "net/http"
require "json"
require "uri"
require "fileutils"

CLIENT_ID="YOUR_CLIENT_ID"

def help
  puts "usage: app_cli <login | whoami | help>"
end

def main
  case ARGV[0]
  when "help"
    help
  when "login"
    login
  when "whoami"
    whoami
  else
    puts "Unknown command #{ARGV[0]}"
  end
end

def parse_response(response)
  case response
  when Net::HTTPOK, Net::HTTPCreated
    JSON.parse(response.body)
  when Net::HTTPUnauthorized
    puts "You are not authorized. Run the `login` command."
    exit 1
  else
    puts response
    puts response.body
    exit 1
  end
end

def request_device_code
  uri = URI("https://github.com/login/device/code")
  parameters = URI.encode_www_form("client_id" => CLIENT_ID)
  headers = {"Accept" => "application/json"}

  response = Net::HTTP.post(uri, parameters, headers)
  parse_response(response)
end

def request_token(device_code)
  uri = URI("https://github.com/login/oauth/access_token")
  parameters = URI.encode_www_form({
    "client_id" => CLIENT_ID,
    "device_code" => device_code,
    "grant_type" => "urn:ietf:params:oauth:grant-type:device_code"
  })
  headers = {"Accept" => "application/json"}
  response = Net::HTTP.post(uri, parameters, headers)
  parse_response(response)
end

def poll_for_token(device_code, interval)

  loop do
    response = request_token(device_code)
    error, access_token = response.values_at("error", "access_token")

    if error
      case error
      when "authorization_pending"
        # The user has not yet entered the code.
        # Wait, then poll again.
        sleep interval
        next
      when "slow_down"
        # The app polled too fast.
        # Wait for the interval plus 5 seconds, then poll again.
        sleep interval + 5
        next
      when "expired_token"
        # The `device_code` expired, and the process needs to restart.
        puts "The device code has expired. Please run `login` again."
        exit 1
      when "access_denied"
        # The user cancelled the process. Stop polling.
        puts "Login cancelled by user."
        exit 1
      else
        puts response
        exit 1
      end
    end

    File.write("./.token", access_token)

    # Set the file permissions so that only the file owner can read or modify the file
    FileUtils.chmod(0600, "./.token")

    break
  end
end

def login
  verification_uri, user_code, device_code, interval = request_device_code.values_at("verification_uri", "user_code", "device_code", "interval")

  puts "Please visit: #{verification_uri}"
  puts "and enter code: #{user_code}"

  poll_for_token(device_code, interval)

  puts "Successfully authenticated!"
end

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

  begin
    token = File.read("./.token").strip
  rescue Errno::ENOENT => e
    puts "You are not authorized. Run the `login` command."
    exit 1
  end

  response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
    body = {"access_token" => token}.to_json
    headers = {"Accept" => "application/vnd.github+json", "Authorization" => "Bearer #{token}"}

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

  parsed_response = parse_response(response)
  puts "You are #{parsed_response["login"]}"
end

main

테스팅

이 자습서에서는 앱 코드가 app_cli.rb라는 파일에 저장된다고 가정합니다.

  1. 터미널에서, app_cli.rb가 저장된 디렉터리에서 ./app_cli.rb help를 실행합니다. 다음과 비슷한 출력이 표시됩니다.

    usage: app_cli <login | whoami | help>
    
  2. 터미널에서, app_cli.rb가 저장된 디렉터리에서 ./app_cli.rb login를 실행합니다. 다음과 비슷한 출력이 표시됩니다. 코드는 매번 다릅니다.

    Please visit: https://github.com/login/device
    and enter code: CA86-8D94
    
  3. 브라우저에서 https://github.com/login/device로 이동하고 이전 단계의 코드를 입력한 다음 계속을 클릭합니다.

  4. GitHub이(가) 앱에 권한을 부여하라는 메시지가 있는 페이지를 표시합니다. "권한 부여" 단추를 클릭합니다.

  5. 이제 터미널에 "성공적으로 인증되었습니다!"라는 메시지가 표시됩니다.

  6. 터미널에서, app_cli.rb가 저장된 디렉터리에서 ./app_cli.rb whoami를 실행합니다. 다음과 같은 출력이 표시됩니다. 여기서 octocat은 사용자의 사용자 이름입니다.

    You are octocat
    
  7. 편집기에서 .token 파일을 열고 토큰을 수정합니다. 이제 토큰이 잘못되었습니다.

  8. 터미널에서, app_cli.rb가 저장된 디렉터리에서 ./app_cli.rb whoami를 실행합니다. 다음과 비슷한 출력이 표시됩니다.

    You are not authorized. Run the `login` command.
    
  9. .token 파일을 삭제합니다.

  10. 터미널에서, app_cli.rb가 저장된 디렉터리에서 ./app_cli.rb whoami를 실행합니다. 다음과 비슷한 출력이 표시됩니다.

    You are not authorized. Run the `login` command.
    

다음 단계

앱의 요구 사항에 맞게 코드 조정

이 자습서에서는 디바이스 흐름을 사용하여 사용자 액세스 토큰을 생성하는 CLI를 작성하는 방법을 설명했습니다. 이 CLI를 확장하여 추가 명령을 받을 수 있습니다. 예를 들어 이슈를 여는 create-issue 명령을 추가할 수 있습니다. 만들려는 API 요청에 대한 추가 권한이 앱에 필요한 경우 앱의 권한을 업데이트해야 합니다. 자세한 내용은 "GitHub 앱의 권한 선택"을(를) 참조하세요.

토큰을 안전하게 저장

이 자습서에서는 사용자 액세스 토큰을 생성하고 로컬 파일에 저장합니다. 이 파일을 커밋하거나 토큰을 공개해서는 안 됩니다.

디바이스에 따라 토큰을 저장하는 다른 방법을 선택할 수 있습니다. 디바이스에 토큰을 저장하는 모범 사례를 참조하시기 바랍니다.

자세한 내용은 "GitHub App을 만드는 모범 사례"을(를) 참조하세요.

모범 사례 준수

GitHub App을(를) 사용하는 모범 사례를 따르는 것을 목표로 해야 합니다. 자세한 내용은 "GitHub App을 만드는 모범 사례"을(를) 참조하세요.