InfoGrab DocsInfoGrab Docs

OAuth 2.0 ID 공급자 API

요약

이 API를 사용하면 서드파티 서비스가 OAuth 2.0 프로토콜로 사용자를 대신해 GitLab 리소스에 접근할 수 있습니다. 이 기능은 doorkeeper Ruby gem을 기반으로 합니다. 다수의 /oauth 엔드포인트가 교차 출처 리소스 공유(CORS)를 지원합니다.

이 API를 사용하면 서드파티 서비스가 OAuth 2.0 프로토콜로 사용자를 대신해 GitLab 리소스에 접근할 수 있습니다. 자세한 내용은 GitLab을 OAuth 2.0 인증 ID 공급자로 구성을 참고합니다.

이 기능은 doorkeeper Ruby gem을 기반으로 합니다.

Cross-origin resource sharing#

다수의 /oauth 엔드포인트가 교차 출처 리소스 공유(CORS)를 지원합니다. 다음 엔드포인트는 CORS 사전 요청(preflight request)도 지원합니다.

  • /oauth/revoke
  • /oauth/token
  • /oauth/userinfo

사전 요청에는 일부 헤더만 사용할 수 있습니다.

예를 들어 X-Requested-With 헤더는 사전 요청에 사용할 수 없습니다.

지원되는 OAuth 2.0 플로#

GitLab은 다음 인가 플로를 지원합니다.

  • Authorization code with Proof Key for Code Exchange (PKCE): 가장 안전합니다. PKCE가 없으면 모바일 클라이언트에 클라이언트 시크릿을 포함해야 합니다. 클라이언트 앱과 서버 앱 모두에 권장하는 플로입니다.
  • Authorization code: 안전하고 널리 쓰이는 플로입니다. 보안이 확보된 서버 사이드 앱에 권장하는 방식입니다.
  • Device Authorization Grant(GitLab 17.1 이상): 브라우저를 사용할 수 없는 기기를 위한 안전한 플로입니다. 인가 플로를 완료하려면 보조 기기가 필요합니다.

OAuth 2.1 초안 명세는 Implicit grant 플로와 Resource Owner Password Credentials 플로를 모두 제외하고 있습니다.

각 플로의 동작 방식과 사용 사례에 맞는 선택 기준은 OAuth RFC를 참고합니다.

Authorization code 플로(PKCE 사용 여부와 무관)를 쓰려면 먼저 사용자 계정의 /user_settings/applications 페이지에서 application을 등록해야 합니다. 등록 과정에서 적절한 스코프를 활성화하면 해당 application 이 접근할 수 있는 리소스 범위를 제한할 수 있습니다. 생성이 끝나면 다음 application 자격 증명을 받습니다.

  • Application ID
  • Client Secret
Warning

Client Secret은 안전하게 보관해야 합니다. 애플리케이션 아키텍처상 가능하다면 Application ID도 비공개로 유지합니다.

GitLab의 스코프 목록은 공급자 문서를 참고합니다.

CSRF 공격 방지#

리다이렉트 기반 플로를 보호하기 위해 OAuth 명세는 "사용자 에이전트에 안전하게 바인딩된, state 파라미터로 전달되는 일회용 CSRF 토큰" 사용을 /oauth/authorize 엔드포인트로 보내는 모든 요청에 권장합니다. 이렇게 하면 CSRF 공격을 방지할 수 있습니다.

프로덕션에서는 HTTPS 사용#

프로덕션에서는 redirect_uri에 HTTPS를 사용합니다. 개발 환경에서는 GitLab 이 안전하지 않은 HTTP 리다이렉트 URI를 허용합니다.

OAuth 2.0은 보안을 전적으로 전송 계층에 의존하므로 보호되지 않은 URI를 사용해서는 안 됩니다. 자세한 내용은 OAuth 2.0 RFC와 OAuth 2.0 위협 모델 RFC를 참고합니다.

다음 섹션에서는 각 플로로 인가를 받는 상세한 방법을 확인할 수 있습니다.

Authorization code with Proof Key for Code Exchange (PKCE)#

PKCE RFC에는 인가 요청부터 액세스 토큰까지의 플로가 상세히 기술되어 있습니다. 다음 단계는 GitLab의 구현 방식을 설명합니다.

줄여서 PKCE 라고 부르는 Authorization code with PKCE 플로를 사용하면 Client Secret 에 전혀 접근하지 않고도 퍼블릭 클라이언트에서 클라이언트 자격 증명을 액세스 토큰으로 안전하게 교환할 수 있습니다. 따라서 PKCE 플로는 단일 페이지 JavaScript 애플리케이션이나 기타 클라이언트 사이드 앱처럼 사용자에게 시크릿을 숨기는 것이 기술적으로 불가능한 환경에 유리합니다.

플로를 시작하기 전에 STATE, CODE_VERIFIER, CODE_CHALLENGE를 생성합니다.

  • STATE는 클라이언트가 요청과 콜백 사이의 상태를 유지하기 위해 사용하는 예측 불가능한 값입니다. CSRF 토큰으로도 사용해야 합니다.
  • CODE_VERIFIER는 길이가 43 자에서 128 자 사이인 임의의 문자열이며, A-Z, a-z, 0-9, -, ., _, ~ 문자를 사용합니다.
  • CODE_CHALLENGE는 CODE_VERIFIER의 SHA256 해시를 URL 안전 base64로 인코딩한 문자열입니다.
    • SHA256 해시는 인코딩 전에 바이너리 형식이어야 합니다.
    • Ruby에서는 Base64.urlsafe_encode64(Digest::SHA256.digest(CODE_VERIFIER), padding: false)로 처리할 수 있습니다.
    • 참고로 ks02i3jdikdo2k0dkfodf3m39rjfjsdk0wk349rj3jrhf라는 CODE_VERIFIER 문자열을 앞의 Ruby 스니펫으로 해시하고 인코딩하면 2i0WFA-0AerkjQm4X4oDEhqA17QIAKNjXpagHBXmO_U라는 CODE_CHALLENGE 문자열이 생성됩니다.
  1. 인가 코드를 요청합니다. 이를 위해 다음 쿼리 파라미터와 함께 사용자를 /oauth/authorize 페이지로 리다이렉트합니다.

    https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code&state=STATE&scope=REQUESTED_SCOPES&code_challenge=CODE_CHALLENGE&code_challenge_method=S256&root_namespace_id=ROOT_NAMESPACE_ID
    

    이 페이지는 REQUESTED_SCOPES에 지정된 스코프를 기준으로 앱이 계정에 접근하도록 요청한 내용을 승인할지 사용자에게 묻습니다. 이후 사용자는 지정된 REDIRECT_URI로 다시 리다이렉트됩니다. 스코프 파라미터는 사용자와 연결된 스코프를 공백으로 구분한 목록입니다. 예를 들어 scope=read_user+profile은 read_user와 profile 스코프를 요청합니다. root_namespace_id는 프로젝트와 연결된 루트 네임스페이스 ID 입니다. 이 선택 파라미터는 해당 그룹에 SAML SSO가 구성된 경우에 사용합니다. 리다이렉트에는 인가 code가 포함됩니다. 예를 들면 다음과 같습니다.

    https://example.com/oauth/redirect?code=1234567890&state=STATE
    
  2. 앞선 요청에서 반환된 인가 code(다음 예제에서는 RETURNED_CODE로 표기)를 사용하면 임의의 HTTP 클라이언트로 access_token을 요청할 수 있습니다. 다음 예제는 Ruby의 rest-client를 사용합니다.

    parameters = 'client_id=APP_ID&code=RETURNED_CODE&grant_type=authorization_code&redirect_uri=REDIRECT_URI&code_verifier=CODE_VERIFIER'
    RestClient.post 'https://gitlab.example.com/oauth/token', parameters
    

    응답 예시:

    {
     "access_token": "de6780bc506a0446309bd9362820ba8aed28aa506c71eedbe1c5c4f9dd350e54",
     "token_type": "bearer",
     "expires_in": 7200,
     "refresh_token": "8257e65c97202ed1726cf9571600918f3bffb2544b26e00a61df9897668c33a1",
     "created_at": 1607635748
    }
    
  3. 새 access_token을 받으려면 refresh_token 파라미터를 사용합니다. 리프레시 토큰은 access_token 자체가 만료된 뒤에도 사용할 수 있습니다. 이 요청은 다음과 같이 동작합니다.

    • 기존 access_token과 refresh_token을 무효화합니다.
    • 응답으로 새 토큰을 전달합니다.
      parameters = 'client_id=APP_ID&refresh_token=REFRESH_TOKEN&grant_type=refresh_token&redirect_uri=REDIRECT_URI'
      RestClient.post 'https://gitlab.example.com/oauth/token', parameters
    

    응답 예시:

    {
      "access_token": "c97d1fe52119f38c7f67f0a14db68d60caa35ddc86fd12401718b649dcfa9c68",
      "token_type": "bearer",
      "expires_in": 7200,
      "refresh_token": "803c1fd487fec35562c205dac93e9d8e08f9d3652a24079d704df3039df1158f",
      "created_at": 1628711391
    }
    
Note

redirect_uri는 최초 인가 요청에 사용한 redirect_uri와 일치해야 합니다.

이제 액세스 토큰으로 API에 요청을 보낼 수 있습니다.

Authorization code 플로#

Note

상세한 플로 설명은 RFC 명세를 확인합니다.

Authorization code 플로는 본질적으로 Authorization code with PKCE 플로와 동일합니다.

플로를 시작하기 전에 STATE를 생성합니다. 이는 클라이언트가 요청과 콜백 사이의 상태를 유지하기 위해 사용하는 예측 불가능한 값입니다. CSRF 토큰으로도 사용해야 합니다.

  1. 인가 코드를 요청합니다. 이를 위해 다음 쿼리 파라미터와 함께 사용자를 /oauth/authorize 페이지로 리다이렉트합니다.

    https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code&state=STATE&scope=REQUESTED_SCOPES&root_namespace_id=ROOT_NAMESPACE_ID
    

    이 페이지는 REQUESTED_SCOPES에 지정된 스코프를 기준으로 앱이 계정에 접근하도록 요청한 내용을 승인할지 사용자에게 묻습니다. 이후 사용자는 지정된 REDIRECT_URI로 다시 리다이렉트됩니다. 스코프 파라미터는 사용자와 연결된 스코프를 공백으로 구분한 목록입니다. 예를 들어 scope=read_user+profile은 read_user와 profile 스코프를 요청합니다. root_namespace_id는 프로젝트와 연결된 루트 네임스페이스 ID 입니다. 이 선택 파라미터는 해당 그룹에 SAML SSO가 구성된 경우에 사용합니다. 리다이렉트에는 인가 code가 포함됩니다. 예를 들면 다음과 같습니다.

    https://example.com/oauth/redirect?code=1234567890&state=STATE
    
  2. 앞선 요청에서 반환된 인가 code(다음 예제에서는 RETURNED_CODE로 표시)를 사용하면 임의의 HTTP 클라이언트로 access_token을 요청할 수 있습니다. 다음 예제는 Ruby의 rest-client를 사용합니다.

    parameters = 'client_id=APP_ID&client_secret=APP_SECRET&code=RETURNED_CODE&grant_type=authorization_code&redirect_uri=REDIRECT_URI'
    RestClient.post 'https://gitlab.example.com/oauth/token', parameters
    

    client_id와 client_secret은 위 예시처럼 요청 파라미터로 보내거나 Authorization: Basic 헤더로 보낼 수 있지만, 둘 다로 보낼 수는 없습니다. 시크릿을 두 곳에 모두 포함한 요청은 400 Bad Request를 반환합니다.

    응답 예시:

    {
     "access_token": "de6780bc506a0446309bd9362820ba8aed28aa506c71eedbe1c5c4f9dd350e54",
     "token_type": "bearer",
     "expires_in": 7200,
     "refresh_token": "8257e65c97202ed1726cf9571600918f3bffb2544b26e00a61df9897668c33a1",
     "created_at": 1607635748
    }
    
  3. 새 access_token을 받으려면 refresh_token 파라미터를 사용합니다. 리프레시 토큰은 access_token 자체가 만료된 뒤에도 사용할 수 있습니다. 이 요청은 다음과 같이 동작합니다.

    • 기존 access_token과 refresh_token을 무효화합니다.
    • 응답으로 새 토큰을 전달합니다.
      parameters = 'client_id=APP_ID&client_secret=APP_SECRET&refresh_token=REFRESH_TOKEN&grant_type=refresh_token&redirect_uri=REDIRECT_URI'
      RestClient.post 'https://gitlab.example.com/oauth/token', parameters
    

    응답 예시:

    {
      "access_token": "c97d1fe52119f38c7f67f0a14db68d60caa35ddc86fd12401718b649dcfa9c68",
      "token_type": "bearer",
      "expires_in": 7200,
      "refresh_token": "803c1fd487fec35562c205dac93e9d8e08f9d3652a24079d704df3039df1158f",
      "created_at": 1628711391
    }
    
Note

redirect_uri는 최초 인가 요청에 사용한 redirect_uri와 일치해야 합니다.

이제 반환된 액세스 토큰으로 API에 요청을 보낼 수 있습니다.

Device authorization grant 플로#

히스토리
Note

기기 인가 요청부터 브라우저 로그인 이후의 토큰 응답까지 device authorization grant 플로에 대한 상세한 설명은 RFC 명세를 확인합니다.

Device authorization grant 플로를 사용하면 브라우저 상호작용이 불가능한 입력 제약 기기에서도 GitLab ID를 안전하게 인증할 수 있습니다.

따라서 device authorization grant 플로는 헤드리스 서버나 UI가 없거나 제한적인 기기에서 GitLab 서비스를 사용하려는 사용자에게 적합합니다.

  1. 기기 인가를 요청할 때는 입력이 제한된 기기 클라이언트에서 https://gitlab.example.com/oauth/authorize_device로 요청을 보냅니다. 예를 들면 다음과 같습니다.

      parameters = 'client_id=UID&scope=read'
      RestClient.post 'https://gitlab.example.com/oauth/authorize_device', parameters
    

    요청에 성공하면 verification_uri가 포함된 응답이 사용자에게 반환됩니다. 예를 들면 다음과 같습니다.

    {
        "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
        "user_code": "0A44L90H",
        "verification_uri": "https://gitlab.example.com/oauth/device",
        "verification_uri_complete": "https://gitlab.example.com/oauth/device?user_code=0A44L90H",
        "expires_in": 300,
        "interval": 5
    }
    
  2. 기기 클라이언트는 응답의 user_code와 verification_uri를 요청 사용자에게 표시합니다. 그러면 해당 사용자는 브라우저를 사용할 수 있는 보조 기기에서 다음을 수행합니다.

    1. 제공된 URI로 이동합니다.
    2. 사용자 코드를 입력합니다.
    3. 안내에 따라 인증을 완료합니다.
  3. verification_uri와 user_code를 표시한 직후, 기기 클라이언트는 최초 응답에서 반환된 device_code로 토큰 엔드포인트를 폴링하기 시작합니다.

    parameters = 'grant_type=urn:ietf:params:oauth:grant-type:device_code
    &device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS
    &client_id=1406020730'
    RestClient.post 'https://gitlab.example.com/oauth/token', parameters
    
  4. 기기 클라이언트는 토큰 엔드포인트에서 응답을 받습니다. 인가에 성공하면 성공 응답이 반환되고, 그렇지 않으면 오류 응답이 반환됩니다. 발생 가능한 오류 응답은 다음 두 가지로 분류됩니다.

    • OAuth Authorization Framework의 액세스 토큰 오류 응답으로 정의된 것.
    • 여기서 설명하는 device authorization grant 플로에 고유한 것.

    기기 플로에 고유한 오류 응답은 다음 내용에서 설명합니다. 각 응답에 대한 자세한 내용은 device authorization grant RFC 명세와 인가 토큰 RFC 명세를 참고합니다.

    응답 예시:

    {
      "error": "authorization_pending",
      "error_description": "..."
    }
    

    이 응답을 받으면 기기 클라이언트는 폴링을 계속합니다.

    폴링 주기가 너무 짧으면 slow down 오류 응답이 반환됩니다. 예를 들면 다음과 같습니다.

    {
      "error": "slow_down",
      "error_description": "..."
    }
    

    이 응답을 받으면 기기 클라이언트는 폴링 주기를 낮추고 새 주기로 폴링을 계속합니다.

    인증이 완료되기 전에 기기 코드가 만료되면 만료된 토큰 오류 응답이 반환됩니다. 예를 들면 다음과 같습니다.

    {
      "error": "expired_token",
      "error_description": "..."
    }
    

    이 시점에 기기 클라이언트는 폴링을 중단하고 새 기기 인가 요청을 시작해야 합니다.

    인가 요청이 거부되면 접근 거부 오류 응답이 반환됩니다. 예를 들면 다음과 같습니다.

    {
      "error": "access_denied",
      "error_description": "..."
    }
    

    인증 요청이 거부된 상태입니다. 사용자는 자격 증명을 확인하거나 시스템 관리자에게 문의합니다

  5. 사용자가 인증에 성공하면 다음과 같은 성공 응답이 반환됩니다.

    {
        "access_token": "TOKEN",
        "token_type": "Bearer",
        "expires_in": 7200,
        "scope": "read",
        "created_at": 1593096829
    }
    

이 시점에 기기 인증 플로가 완료됩니다. 반환된 access_token은 HTTPS로 클론하거나 API에 접근하는 등 GitLab 리소스를 사용할 때 사용자 ID를 인증하는 용도로 GitLab에 제공할 수 있습니다.

클라이언트 측 기기 플로를 구현한 샘플 애플리케이션은 https://gitlab.com/johnwparent/git-auth-over-https에서 확인할 수 있습니다.

access token으로 GitLab API 접근#

access token을 사용하면 사용자를 대신해 API에 요청을 보낼 수 있습니다. 토큰은 GET 파라미터로 전달하거나 Authorization 헤더에 넣을 수 있지만, 둘 다로 보낼 수는 없습니다. 토큰을 두 곳에 모두 포함한 요청은 400 Bad Request를 반환합니다.

GET 파라미터로 전달하는 경우:

GET https://gitlab.example.com/api/v4/user?access_token=

Authorization 헤더에 넣는 경우:

curl --request GET \
  --header "Authorization: Bearer " \
  --url "https://gitlab.example.com/api/v4/user"

access token으로 HTTPS를 통한 Git 접근#

스코프가 read_repository 또는 write_repository 인 토큰은 HTTPS로 Git에 접근할 수 있습니다. 토큰을 비밀번호로 사용합니다. 사용자 이름은 어떤 문자열로도 설정할 수 있으며, oauth2 사용을 권장합니다.

https://oauth2:<your_access_token>@gitlab.example.com/project_path/project_name.git

또는 Git 자격 증명 헬퍼를 사용해 OAuth로 GitLab에 인증할 수 있습니다. 이 경우 OAuth 토큰 갱신이 자동으로 처리됩니다.

토큰 정보 조회#

토큰의 상세 정보를 확인하려면 Doorkeeper gem 이 제공하는 token/info 엔드포인트를 사용합니다. 자세한 내용은 /oauth/token/info를 참고합니다.

액세스 토큰은 다음 두 가지 방법 중 하나로 전달해야 합니다.

  • 파라미터로 전달합니다.

    GET https://gitlab.example.com/oauth/token/info?access_token=
    
  • Authorization 헤더에 넣습니다.

    curl --request GET \
      --header "Authorization: Bearer " \
      --url "https://gitlab.example.com/oauth/token/info"
    

다음은 응답 예시입니다.

{
    "resource_owner_id": 1,
    "scope": ["api"],
    "expires_in": null,
    "application": {"uid": "1cb242f495280beb4291e64bee2a17f330902e499882fe8e1e2aa875519cab33"},
    "created_at": 1575890427
}

더 이상 사용되지 않는 필드#

scopes와 expires_in_seconds 필드는 응답에 포함되지만 현재는 더 이상 사용되지 않습니다. scopes 필드는 scope의 별칭이고, expires_in_seconds 필드는 expires_in의 별칭입니다. 자세한 내용은 Doorkeeper API 변경 사항을 참고합니다.

토큰 폐기#

토큰을 폐기하려면 revoke 엔드포인트를 사용합니다. 성공 시 API는 200 응답 코드와 빈 JSON 해시를 반환합니다.

parameters = 'client_id=APP_ID&client_secret=APP_SECRET&token=TOKEN'
RestClient.post 'https://gitlab.example.com/oauth/revoke', parameters

OAuth 2.0 토큰과 GitLab 레지스트리#

표준 OAuth 2.0 토큰은 GitLab 레지스트리에 대해 서로 다른 수준의 접근을 지원합니다. 구체적으로는 다음과 같습니다.

OAuth 2.0 ID 공급자 API

GitLab v19.5
Tier: Free, Premium, Ultimate
Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
원문 보기

요약

이 API를 사용하면 서드파티 서비스가 OAuth 2.0 프로토콜로 사용자를 대신해 GitLab 리소스에 접근할 수 있습니다. 이 기능은 doorkeeper Ruby gem을 기반으로 합니다. 다수의 /oauth 엔드포인트가 교차 출처 리소스 공유(CORS)를 지원합니다.

이 API를 사용하면 서드파티 서비스가 OAuth 2.0 프로토콜로 사용자를 대신해 GitLab 리소스에 접근할 수 있습니다. 자세한 내용은 GitLab을 OAuth 2.0 인증 ID 공급자로 구성을 참고합니다.

이 기능은 doorkeeper Ruby gem을 기반으로 합니다.

Cross-origin resource sharing#

다수의 /oauth 엔드포인트가 교차 출처 리소스 공유(CORS)를 지원합니다. 다음 엔드포인트는 CORS 사전 요청(preflight request)도 지원합니다.

  • /oauth/revoke
  • /oauth/token
  • /oauth/userinfo

사전 요청에는 일부 헤더만 사용할 수 있습니다.

예를 들어 X-Requested-With 헤더는 사전 요청에 사용할 수 없습니다.

지원되는 OAuth 2.0 플로#

GitLab은 다음 인가 플로를 지원합니다.

  • Authorization code with Proof Key for Code Exchange (PKCE): 가장 안전합니다. PKCE가 없으면 모바일 클라이언트에 클라이언트 시크릿을 포함해야 합니다. 클라이언트 앱과 서버 앱 모두에 권장하는 플로입니다.
  • Authorization code: 안전하고 널리 쓰이는 플로입니다. 보안이 확보된 서버 사이드 앱에 권장하는 방식입니다.
  • Device Authorization Grant(GitLab 17.1 이상): 브라우저를 사용할 수 없는 기기를 위한 안전한 플로입니다. 인가 플로를 완료하려면 보조 기기가 필요합니다.

OAuth 2.1 초안 명세는 Implicit grant 플로와 Resource Owner Password Credentials 플로를 모두 제외하고 있습니다.

각 플로의 동작 방식과 사용 사례에 맞는 선택 기준은 OAuth RFC를 참고합니다.

Authorization code 플로(PKCE 사용 여부와 무관)를 쓰려면 먼저 사용자 계정의 /user_settings/applications 페이지에서 application을 등록해야 합니다. 등록 과정에서 적절한 스코프를 활성화하면 해당 application 이 접근할 수 있는 리소스 범위를 제한할 수 있습니다. 생성이 끝나면 다음 application 자격 증명을 받습니다.

  • Application ID
  • Client Secret
Warning

Client Secret은 안전하게 보관해야 합니다. 애플리케이션 아키텍처상 가능하다면 Application ID도 비공개로 유지합니다.

GitLab의 스코프 목록은 공급자 문서를 참고합니다.

CSRF 공격 방지#

리다이렉트 기반 플로를 보호하기 위해 OAuth 명세는 "사용자 에이전트에 안전하게 바인딩된, state 파라미터로 전달되는 일회용 CSRF 토큰" 사용을 /oauth/authorize 엔드포인트로 보내는 모든 요청에 권장합니다. 이렇게 하면 CSRF 공격을 방지할 수 있습니다.

프로덕션에서는 HTTPS 사용#

프로덕션에서는 redirect_uri에 HTTPS를 사용합니다. 개발 환경에서는 GitLab 이 안전하지 않은 HTTP 리다이렉트 URI를 허용합니다.

OAuth 2.0은 보안을 전적으로 전송 계층에 의존하므로 보호되지 않은 URI를 사용해서는 안 됩니다. 자세한 내용은 OAuth 2.0 RFC와 OAuth 2.0 위협 모델 RFC를 참고합니다.

다음 섹션에서는 각 플로로 인가를 받는 상세한 방법을 확인할 수 있습니다.

Authorization code with Proof Key for Code Exchange (PKCE)#

PKCE RFC에는 인가 요청부터 액세스 토큰까지의 플로가 상세히 기술되어 있습니다. 다음 단계는 GitLab의 구현 방식을 설명합니다.

줄여서 PKCE 라고 부르는 Authorization code with PKCE 플로를 사용하면 Client Secret 에 전혀 접근하지 않고도 퍼블릭 클라이언트에서 클라이언트 자격 증명을 액세스 토큰으로 안전하게 교환할 수 있습니다. 따라서 PKCE 플로는 단일 페이지 JavaScript 애플리케이션이나 기타 클라이언트 사이드 앱처럼 사용자에게 시크릿을 숨기는 것이 기술적으로 불가능한 환경에 유리합니다.

플로를 시작하기 전에 STATE, CODE_VERIFIER, CODE_CHALLENGE를 생성합니다.

  • STATE는 클라이언트가 요청과 콜백 사이의 상태를 유지하기 위해 사용하는 예측 불가능한 값입니다. CSRF 토큰으로도 사용해야 합니다.
  • CODE_VERIFIER는 길이가 43 자에서 128 자 사이인 임의의 문자열이며, A-Z, a-z, 0-9, -, ., _, ~ 문자를 사용합니다.
  • CODE_CHALLENGE는 CODE_VERIFIER의 SHA256 해시를 URL 안전 base64로 인코딩한 문자열입니다.
    • SHA256 해시는 인코딩 전에 바이너리 형식이어야 합니다.
    • Ruby에서는 Base64.urlsafe_encode64(Digest::SHA256.digest(CODE_VERIFIER), padding: false)로 처리할 수 있습니다.
    • 참고로 ks02i3jdikdo2k0dkfodf3m39rjfjsdk0wk349rj3jrhf라는 CODE_VERIFIER 문자열을 앞의 Ruby 스니펫으로 해시하고 인코딩하면 2i0WFA-0AerkjQm4X4oDEhqA17QIAKNjXpagHBXmO_U라는 CODE_CHALLENGE 문자열이 생성됩니다.
  1. 인가 코드를 요청합니다. 이를 위해 다음 쿼리 파라미터와 함께 사용자를 /oauth/authorize 페이지로 리다이렉트합니다.

    https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code&state=STATE&scope=REQUESTED_SCOPES&code_challenge=CODE_CHALLENGE&code_challenge_method=S256&root_namespace_id=ROOT_NAMESPACE_ID
    

    이 페이지는 REQUESTED_SCOPES에 지정된 스코프를 기준으로 앱이 계정에 접근하도록 요청한 내용을 승인할지 사용자에게 묻습니다. 이후 사용자는 지정된 REDIRECT_URI로 다시 리다이렉트됩니다. 스코프 파라미터는 사용자와 연결된 스코프를 공백으로 구분한 목록입니다. 예를 들어 scope=read_user+profile은 read_user와 profile 스코프를 요청합니다. root_namespace_id는 프로젝트와 연결된 루트 네임스페이스 ID 입니다. 이 선택 파라미터는 해당 그룹에 SAML SSO가 구성된 경우에 사용합니다. 리다이렉트에는 인가 code가 포함됩니다. 예를 들면 다음과 같습니다.

    https://example.com/oauth/redirect?code=1234567890&state=STATE
    
  2. 앞선 요청에서 반환된 인가 code(다음 예제에서는 RETURNED_CODE로 표기)를 사용하면 임의의 HTTP 클라이언트로 access_token을 요청할 수 있습니다. 다음 예제는 Ruby의 rest-client를 사용합니다.

    parameters = 'client_id=APP_ID&code=RETURNED_CODE&grant_type=authorization_code&redirect_uri=REDIRECT_URI&code_verifier=CODE_VERIFIER'
    RestClient.post 'https://gitlab.example.com/oauth/token', parameters
    

    응답 예시:

    {
     "access_token": "de6780bc506a0446309bd9362820ba8aed28aa506c71eedbe1c5c4f9dd350e54",
     "token_type": "bearer",
     "expires_in": 7200,
     "refresh_token": "8257e65c97202ed1726cf9571600918f3bffb2544b26e00a61df9897668c33a1",
     "created_at": 1607635748
    }
    
  3. 새 access_token을 받으려면 refresh_token 파라미터를 사용합니다. 리프레시 토큰은 access_token 자체가 만료된 뒤에도 사용할 수 있습니다. 이 요청은 다음과 같이 동작합니다.

    • 기존 access_token과 refresh_token을 무효화합니다.
    • 응답으로 새 토큰을 전달합니다.
      parameters = 'client_id=APP_ID&refresh_token=REFRESH_TOKEN&grant_type=refresh_token&redirect_uri=REDIRECT_URI'
      RestClient.post 'https://gitlab.example.com/oauth/token', parameters
    

    응답 예시:

    {
      "access_token": "c97d1fe52119f38c7f67f0a14db68d60caa35ddc86fd12401718b649dcfa9c68",
      "token_type": "bearer",
      "expires_in": 7200,
      "refresh_token": "803c1fd487fec35562c205dac93e9d8e08f9d3652a24079d704df3039df1158f",
      "created_at": 1628711391
    }
    
Note

redirect_uri는 최초 인가 요청에 사용한 redirect_uri와 일치해야 합니다.

이제 액세스 토큰으로 API에 요청을 보낼 수 있습니다.

Authorization code 플로#

Note

상세한 플로 설명은 RFC 명세를 확인합니다.

Authorization code 플로는 본질적으로 Authorization code with PKCE 플로와 동일합니다.

플로를 시작하기 전에 STATE를 생성합니다. 이는 클라이언트가 요청과 콜백 사이의 상태를 유지하기 위해 사용하는 예측 불가능한 값입니다. CSRF 토큰으로도 사용해야 합니다.

  1. 인가 코드를 요청합니다. 이를 위해 다음 쿼리 파라미터와 함께 사용자를 /oauth/authorize 페이지로 리다이렉트합니다.

    https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code&state=STATE&scope=REQUESTED_SCOPES&root_namespace_id=ROOT_NAMESPACE_ID
    

    이 페이지는 REQUESTED_SCOPES에 지정된 스코프를 기준으로 앱이 계정에 접근하도록 요청한 내용을 승인할지 사용자에게 묻습니다. 이후 사용자는 지정된 REDIRECT_URI로 다시 리다이렉트됩니다. 스코프 파라미터는 사용자와 연결된 스코프를 공백으로 구분한 목록입니다. 예를 들어 scope=read_user+profile은 read_user와 profile 스코프를 요청합니다. root_namespace_id는 프로젝트와 연결된 루트 네임스페이스 ID 입니다. 이 선택 파라미터는 해당 그룹에 SAML SSO가 구성된 경우에 사용합니다. 리다이렉트에는 인가 code가 포함됩니다. 예를 들면 다음과 같습니다.

    https://example.com/oauth/redirect?code=1234567890&state=STATE
    
  2. 앞선 요청에서 반환된 인가 code(다음 예제에서는 RETURNED_CODE로 표시)를 사용하면 임의의 HTTP 클라이언트로 access_token을 요청할 수 있습니다. 다음 예제는 Ruby의 rest-client를 사용합니다.

    parameters = 'client_id=APP_ID&client_secret=APP_SECRET&code=RETURNED_CODE&grant_type=authorization_code&redirect_uri=REDIRECT_URI'
    RestClient.post 'https://gitlab.example.com/oauth/token', parameters
    

    client_id와 client_secret은 위 예시처럼 요청 파라미터로 보내거나 Authorization: Basic 헤더로 보낼 수 있지만, 둘 다로 보낼 수는 없습니다. 시크릿을 두 곳에 모두 포함한 요청은 400 Bad Request를 반환합니다.

    응답 예시:

    {
     "access_token": "de6780bc506a0446309bd9362820ba8aed28aa506c71eedbe1c5c4f9dd350e54",
     "token_type": "bearer",
     "expires_in": 7200,
     "refresh_token": "8257e65c97202ed1726cf9571600918f3bffb2544b26e00a61df9897668c33a1",
     "created_at": 1607635748
    }
    
  3. 새 access_token을 받으려면 refresh_token 파라미터를 사용합니다. 리프레시 토큰은 access_token 자체가 만료된 뒤에도 사용할 수 있습니다. 이 요청은 다음과 같이 동작합니다.

    • 기존 access_token과 refresh_token을 무효화합니다.
    • 응답으로 새 토큰을 전달합니다.
      parameters = 'client_id=APP_ID&client_secret=APP_SECRET&refresh_token=REFRESH_TOKEN&grant_type=refresh_token&redirect_uri=REDIRECT_URI'
      RestClient.post 'https://gitlab.example.com/oauth/token', parameters
    

    응답 예시:

    {
      "access_token": "c97d1fe52119f38c7f67f0a14db68d60caa35ddc86fd12401718b649dcfa9c68",
      "token_type": "bearer",
      "expires_in": 7200,
      "refresh_token": "803c1fd487fec35562c205dac93e9d8e08f9d3652a24079d704df3039df1158f",
      "created_at": 1628711391
    }
    
Note

redirect_uri는 최초 인가 요청에 사용한 redirect_uri와 일치해야 합니다.

이제 반환된 액세스 토큰으로 API에 요청을 보낼 수 있습니다.

Device authorization grant 플로#

히스토리
Note

기기 인가 요청부터 브라우저 로그인 이후의 토큰 응답까지 device authorization grant 플로에 대한 상세한 설명은 RFC 명세를 확인합니다.

Device authorization grant 플로를 사용하면 브라우저 상호작용이 불가능한 입력 제약 기기에서도 GitLab ID를 안전하게 인증할 수 있습니다.

따라서 device authorization grant 플로는 헤드리스 서버나 UI가 없거나 제한적인 기기에서 GitLab 서비스를 사용하려는 사용자에게 적합합니다.

  1. 기기 인가를 요청할 때는 입력이 제한된 기기 클라이언트에서 https://gitlab.example.com/oauth/authorize_device로 요청을 보냅니다. 예를 들면 다음과 같습니다.

      parameters = 'client_id=UID&scope=read'
      RestClient.post 'https://gitlab.example.com/oauth/authorize_device', parameters
    

    요청에 성공하면 verification_uri가 포함된 응답이 사용자에게 반환됩니다. 예를 들면 다음과 같습니다.

    {
        "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
        "user_code": "0A44L90H",
        "verification_uri": "https://gitlab.example.com/oauth/device",
        "verification_uri_complete": "https://gitlab.example.com/oauth/device?user_code=0A44L90H",
        "expires_in": 300,
        "interval": 5
    }
    
  2. 기기 클라이언트는 응답의 user_code와 verification_uri를 요청 사용자에게 표시합니다. 그러면 해당 사용자는 브라우저를 사용할 수 있는 보조 기기에서 다음을 수행합니다.

    1. 제공된 URI로 이동합니다.
    2. 사용자 코드를 입력합니다.
    3. 안내에 따라 인증을 완료합니다.
  3. verification_uri와 user_code를 표시한 직후, 기기 클라이언트는 최초 응답에서 반환된 device_code로 토큰 엔드포인트를 폴링하기 시작합니다.

    parameters = 'grant_type=urn:ietf:params:oauth:grant-type:device_code
    &device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS
    &client_id=1406020730'
    RestClient.post 'https://gitlab.example.com/oauth/token', parameters
    
  4. 기기 클라이언트는 토큰 엔드포인트에서 응답을 받습니다. 인가에 성공하면 성공 응답이 반환되고, 그렇지 않으면 오류 응답이 반환됩니다. 발생 가능한 오류 응답은 다음 두 가지로 분류됩니다.

    • OAuth Authorization Framework의 액세스 토큰 오류 응답으로 정의된 것.
    • 여기서 설명하는 device authorization grant 플로에 고유한 것.

    기기 플로에 고유한 오류 응답은 다음 내용에서 설명합니다. 각 응답에 대한 자세한 내용은 device authorization grant RFC 명세와 인가 토큰 RFC 명세를 참고합니다.

    응답 예시:

    {
      "error": "authorization_pending",
      "error_description": "..."
    }
    

    이 응답을 받으면 기기 클라이언트는 폴링을 계속합니다.

    폴링 주기가 너무 짧으면 slow down 오류 응답이 반환됩니다. 예를 들면 다음과 같습니다.

    {
      "error": "slow_down",
      "error_description": "..."
    }
    

    이 응답을 받으면 기기 클라이언트는 폴링 주기를 낮추고 새 주기로 폴링을 계속합니다.

    인증이 완료되기 전에 기기 코드가 만료되면 만료된 토큰 오류 응답이 반환됩니다. 예를 들면 다음과 같습니다.

    {
      "error": "expired_token",
      "error_description": "..."
    }
    

    이 시점에 기기 클라이언트는 폴링을 중단하고 새 기기 인가 요청을 시작해야 합니다.

    인가 요청이 거부되면 접근 거부 오류 응답이 반환됩니다. 예를 들면 다음과 같습니다.

    {
      "error": "access_denied",
      "error_description": "..."
    }
    

    인증 요청이 거부된 상태입니다. 사용자는 자격 증명을 확인하거나 시스템 관리자에게 문의합니다

  5. 사용자가 인증에 성공하면 다음과 같은 성공 응답이 반환됩니다.

    {
        "access_token": "TOKEN",
        "token_type": "Bearer",
        "expires_in": 7200,
        "scope": "read",
        "created_at": 1593096829
    }
    

이 시점에 기기 인증 플로가 완료됩니다. 반환된 access_token은 HTTPS로 클론하거나 API에 접근하는 등 GitLab 리소스를 사용할 때 사용자 ID를 인증하는 용도로 GitLab에 제공할 수 있습니다.

클라이언트 측 기기 플로를 구현한 샘플 애플리케이션은 https://gitlab.com/johnwparent/git-auth-over-https에서 확인할 수 있습니다.

access token으로 GitLab API 접근#

access token을 사용하면 사용자를 대신해 API에 요청을 보낼 수 있습니다. 토큰은 GET 파라미터로 전달하거나 Authorization 헤더에 넣을 수 있지만, 둘 다로 보낼 수는 없습니다. 토큰을 두 곳에 모두 포함한 요청은 400 Bad Request를 반환합니다.

GET 파라미터로 전달하는 경우:

GET https://gitlab.example.com/api/v4/user?access_token=

Authorization 헤더에 넣는 경우:

curl --request GET \
  --header "Authorization: Bearer " \
  --url "https://gitlab.example.com/api/v4/user"

access token으로 HTTPS를 통한 Git 접근#

스코프가 read_repository 또는 write_repository 인 토큰은 HTTPS로 Git에 접근할 수 있습니다. 토큰을 비밀번호로 사용합니다. 사용자 이름은 어떤 문자열로도 설정할 수 있으며, oauth2 사용을 권장합니다.

https://oauth2:<your_access_token>@gitlab.example.com/project_path/project_name.git

또는 Git 자격 증명 헬퍼를 사용해 OAuth로 GitLab에 인증할 수 있습니다. 이 경우 OAuth 토큰 갱신이 자동으로 처리됩니다.

토큰 정보 조회#

토큰의 상세 정보를 확인하려면 Doorkeeper gem 이 제공하는 token/info 엔드포인트를 사용합니다. 자세한 내용은 /oauth/token/info를 참고합니다.

액세스 토큰은 다음 두 가지 방법 중 하나로 전달해야 합니다.

  • 파라미터로 전달합니다.

    GET https://gitlab.example.com/oauth/token/info?access_token=
    
  • Authorization 헤더에 넣습니다.

    curl --request GET \
      --header "Authorization: Bearer " \
      --url "https://gitlab.example.com/oauth/token/info"
    

다음은 응답 예시입니다.

{
    "resource_owner_id": 1,
    "scope": ["api"],
    "expires_in": null,
    "application": {"uid": "1cb242f495280beb4291e64bee2a17f330902e499882fe8e1e2aa875519cab33"},
    "created_at": 1575890427
}

더 이상 사용되지 않는 필드#

scopes와 expires_in_seconds 필드는 응답에 포함되지만 현재는 더 이상 사용되지 않습니다. scopes 필드는 scope의 별칭이고, expires_in_seconds 필드는 expires_in의 별칭입니다. 자세한 내용은 Doorkeeper API 변경 사항을 참고합니다.

토큰 폐기#

토큰을 폐기하려면 revoke 엔드포인트를 사용합니다. 성공 시 API는 200 응답 코드와 빈 JSON 해시를 반환합니다.

parameters = 'client_id=APP_ID&client_secret=APP_SECRET&token=TOKEN'
RestClient.post 'https://gitlab.example.com/oauth/revoke', parameters

OAuth 2.0 토큰과 GitLab 레지스트리#

표준 OAuth 2.0 토큰은 GitLab 레지스트리에 대해 서로 다른 수준의 접근을 지원합니다. 구체적으로는 다음과 같습니다.