OpenID Connect (OIDC) Authentication Using ID Tokens

  • Tier: Free, Premium, Ultimate
  • Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
History

You can authenticate with third party services using GitLab CI/CD’s ID tokens.

ID Tokens

ID tokens are JSON Web Tokens (JWTs) that can be added to a GitLab CI/CD job. They can be used for OIDC authentication with third-party services, and are used by the secrets keyword to authenticate with HashiCorp Vault.

ID tokens are configured in the .gitlab-ci.yml. For example:

job_with_id_tokens:
  id_tokens:
    FIRST_ID_TOKEN:
      aud: https://first.service.com
    SECOND_ID_TOKEN:
      aud: https://second.service.com
  script:
    - first-service-authentication-script.sh $FIRST_ID_TOKEN
    - second-service-authentication-script.sh $SECOND_ID_TOKEN

In this example, the two tokens have different aud claims. Third party services can be configured to reject tokens that do not have an aud claim matching their bound audience. Use this functionality to reduce the number of services with which a token can authenticate. This reduces the severity of having a token compromised.

Token payload

The following standard claims are included in each ID token:

FieldDescription
issIssuer of the token, which is the domain of the GitLab instance (“issuer” claim).
subSubject of the token (“subject” claim). Defaults to project_path:{group}/{project}:ref_type:{type}:ref:{branch_name}. Can be configured for the project with the projects API.
audIntended audience for the token (“audience” claim). Specified in the ID tokens configuration. The domain of the GitLab instance by default.
expThe expiration time (“expiration time” claim).
nbfThe time after which the token becomes valid (“not before” claim).
iatThe time the JWT was issued (“issued at” claim).
jtiUnique identifier for the token (“JWT ID” claim).

The token also includes custom claims provided by GitLab:

FieldWhenDescription
namespace_idAlwaysUse this to scope to group or user level namespace by ID.
namespace_pathAlwaysUse this to scope to group or user level namespace by path.
project_idAlwaysUse this to scope to project by ID.
project_pathAlwaysUse this to scope to project by path.
user_idAlwaysID of the user executing the job.
user_loginAlwaysUsername of the user executing the job.
user_emailAlwaysEmail of the user executing the job.
user_access_levelAlwaysAccess level of the user executing the job. Introduced in GitLab 16.9.
user_identitiesUser Preference settingList of the user’s external identities (introduced in GitLab 16.0).
pipeline_idAlwaysID of the pipeline.
pipeline_sourceAlwaysPipeline source.
job_idAlwaysID of the job.
refAlwaysGit ref for the job.
ref_typeAlwaysGit ref type, either branch or tag.
ref_pathAlwaysFully qualified ref for the job. For example, refs/heads/main. Introduced in GitLab 16.0.
ref_protectedAlwaystrue if the Git ref is protected, false otherwise.
groups_directUser is a direct member of 0 to 200 groupsThe paths of the user’s direct membership groups. Omitted if the user is a direct member of more than 200 groups. (Introduced in GitLab 16.11 and put behind the ci_jwt_groups_direct feature flag in GitLab 17.3.
environmentJob specifies an environmentEnvironment this job deploys to.
environment_protectedJob specifies an environmenttrue if deployed environment is protected, false otherwise.
deployment_tierJob specifies an environmentDeployment tier of the environment the job specifies. Introduced in GitLab 15.2.
environment_actionJob specifies an environmentEnvironment action (environment:action) specified in the job. (Introduced in GitLab 16.5)
runner_idAlwaysID of the runner executing the job. Introduced in GitLab 16.0.
runner_environmentAlwaysThe type of runner used by the job. Can be either gitlab-hosted or self-hosted. Introduced in GitLab 16.0.
shaAlwaysThe commit SHA for the job. Introduced in GitLab 16.0.
ci_config_ref_uriAlwaysThe ref path to the top-level pipeline definition, for example, gitlab.example.com/my-group/my-project//.gitlab-ci.yml@refs/heads/main. Introduced in GitLab 16.2. This claim is null unless the pipeline definition is located in the same project.
ci_config_shaAlwaysGit commit SHA for the ci_config_ref_uri. Introduced in GitLab 16.2. This claim is null unless the pipeline definition is located in the same project.
project_visibilityAlwaysThe visibility of the project where the pipeline is running. Can be internal, private, or public. Introduced in GitLab 16.3.
{
  "namespace_id": "72",
  "namespace_path": "my-group",
  "project_id": "20",
  "project_path": "my-group/my-project",
  "user_id": "1",
  "user_login": "sample-user",
  "user_email": "sample-user@example.com",
  "user_identities": [
      {"provider": "github", "extern_uid": "2435223452345"},
      {"provider": "bitbucket", "extern_uid": "john.smith"}
  ],
  "pipeline_id": "574",
  "pipeline_source": "push",
  "job_id": "302",
  "ref": "feature-branch-1",
  "ref_type": "branch",
  "ref_path": "refs/heads/feature-branch-1",
  "ref_protected": "false",
  "groups_direct": ["mygroup/mysubgroup", "myothergroup/myothersubgroup"],
  "environment": "test-environment2",
  "environment_protected": "false",
  "deployment_tier": "testing",
  "environment_action": "start",
  "runner_id": 1,
  "runner_environment": "self-hosted",
  "sha": "714a629c0b401fdce83e847fc9589983fc6f46bc",
  "project_visibility": "public",
  "ci_config_ref_uri": "gitlab.example.com/my-group/my-project//.gitlab-ci.yml@refs/heads/main",
  "ci_config_sha": "714a629c0b401fdce83e847fc9589983fc6f46bc",
  "jti": "235b3a54-b797-45c7-ae9a-f72d7bc6ef5b",
  "iss": "https://gitlab.example.com",
  "iat": 1681395193,
  "nbf": 1681395188,
  "exp": 1681398793,
  "sub": "project_path:my-group/my-project:ref_type:branch:ref:feature-branch-1",
  "aud": "https://vault.example.com"
}

The ID token is encoded by using RS256 and signed with a dedicated private key. The expiry time for the token is set to the job’s timeout if specified, or 5 minutes if no timeout is specified.

ID Token authentication with third party services

You can use ID tokens for OIDC authentication with a third party service. For example:

Troubleshooting

400: missing token status code

This error indicates that one or more basic components necessary for ID tokens are either missing or not configured as expected.

To find the problem, an administrator can look for more details in the instance’s exceptions_json.log for the specific method that failed.

GitLab::Ci::Jwt::NoSigningKeyError

This error in the exceptions_json.log file is likely because the signing key is missing from the database and the token could not be generated. To verify this is the issue, run the following query on the instance’s PostgreSQL terminal:

SELECT encrypted_ci_jwt_signing_key FROM application_settings;

If the returned value is empty, use the Rails snippet below to generate a new key and replace it internally:

  key = OpenSSL::PKey::RSA.new(2048).to_pem

  ApplicationSetting.find_each do |application_setting|
    application_setting.update(ci_jwt_signing_key: key)
  end