Description
The following use case demonstrates how a GitHub organization can integrate with JFrog Artifactory using OIDC. In this setup, developers deploy to a development repository from Feature Branch commits, while a service account with elevated permissions deploys to a release repository from Main Branch commits. Throughout the process, traceability to the GitHub actor or committer is maintained. This example illustrates how to configure dynamic identity mapping, apply wildcards in claims, and use priority rules effectively.
For reference, see the GitHub Actions Workflow script used for this example.
Using User Mapping for Main Branch Commits
In this use case, deployments from the main branch are configured to resolve against a user account derived from the GitHub repository_owner claim. This allows the “Deployed By” field in Artifactory to display a meaningful user (rather than a generic service account or provider reference) while still applying controlled permissions.
An identity mapping named all-main-branch-commits was created with the following parameters:
{
"name": "all-main-branch-commits",
"provider_name": "jeremyl-frog",
"claims": {
"ref": "refs/heads/main"
},
"token_spec": {
"scope": "applied-permissions/user",
"username_pattern": "{{repository_owner}}"
},
"priority": 1
}
On the Artifactory side, a user account matching the repository owner (jeremyl-frog) was created and added to the jl-github-ci permission group. After running the workflow, deployments from the main branch now appear as Deployed By: jeremyl-frog in Artifactory.
Artifact properties added in the workflow (such as committer and GitHub actor) remain intact, so traceability to the individual who triggered the workflow is always preserved.
Using Group Mapping for Dev Branch Commits
In this use case, deployments from feature branches are configured to resolve against a group assignment rather than a specific user. This ensures that commits made to development branches deploy artifacts under a limited-permission group while still maintaining traceability through the Artifact properties added in the workflow.
An identity mapping named all-dev-branch-commits was created with the following parameters:
{
"name": "all-dev-branch-commits",
"provider_name": "jeremyl-frog",
"claims": {
"ref": "refs/heads/*"
},
"token_spec": {
"scope": "applied-permissions/groups:frog-company-devs"
},
"priority": 2
}
The Wildcard ( * ) in the claim ("ref": "refs/heads/*") will match the identity mapping to Commits that are triggered from any Branch.
On the Artifactory side, a group named frog-company-devs was created and granted developer-level permissions to the dev repository. After running the workflow, deployments from any branch other than main (explained in the Note on priority below) now appear as deployed by a token with the format (token:<provider>/<actor>). It's expected that the actor is already associated with the frog-company-devs group.
Note on priority
Since multiple mappings can apply, priority ensures the intended one is evaluated first. In this case, the main branch mapping is set to priority 1, so it always takes precedence over the dev branch mapping at priority 2. This guarantees that commits from refs/heads/main are resolved against the main branch rule, while all other branches are matched by the dev branch rule.
Dynamic Group Claims with IdPs
In scenarios with enterprise IdPs (e.g., Entra ID, Okta), dynamic group mapping can be used instead of static mapping. For example, the IdP may emit a group or groups claim containing one or more values, which can be attached to the token scope and resolved into Artifactory groups dynamically. GitHub does not provide such a claim in its OIDC tokens, so an explicit, static group called frog-company-devs is assigned in our above example..
## The below is for illustration, (based on this example in our Dynamic Mappings doc):
If an IdP token includes the claim:
"groups": "devs,admins,qa"
and the mapping uses:
"groups_pattern": "{{groups}}"
then Artifactory would resolve this into:
applied-permissions/groups:"devs","admins","qa"
Pattern Flexibility in Dynamic Mappings
Beyond directly mapping claims to users or groups, identity mappings in Artifactory support transformation patterns. These allow claim values to be modified on the fly—adding suffixes, stripping domains, or normalizing formats.
A mapping pattern has two parts:
- Before the | → the pattern to match against the claim value.
- {{actor}} or {{mail}} means “take the raw claim value.”
- Wrapping with parentheses () defines capture groups (sub-sections of the value you want to extract).
- After the | → the transformation rule, using:
- $0 = the entire match from the left side.
- $1, $2, … = the contents of the first, second, etc. capture groups.
Think of it as:
Claim value → Match (before |) → Rewrite (after |) → Final mapped value
Examples:
// Example 1: Append a domain suffix
"username_pattern": "{{actor}} | $0@jfrog.com"
// actor = "saidegreen"
// Match: $0 = "saidegreen"
// Result → applied-permissions/user:saidegreen@jfrog.com
// Example 2: Strip a domain
"username_pattern": "({{actor}})@jfrog.com | $1"
// actor = "saidegreen@jfrog.com"
// Match: $0 = "saidegreen@jfrog.com", $1 = "saidegreen"
// Result → applied-permissions/user:saidegreen
// Example 3: Add a suffix to groups
"groups_pattern": "{{group}} | $0-jfrog"
// group = "gitgroup"
// Match: $0 = "gitgroup"
// Result → applied-permissions/groups:"gitgroup-jfrog"
// Example 4: Match and preserve a full email
"username_pattern": "jf{{mail}}jfrog.com | $0"
// mail = "jfsaidegreen@jfrog.com"
// Match: $0 = "jfsaidegreen@jfrog.com"
// Result → applied-permissions/user:jfsaidegreen@jfrog.com
// Example 5: No match case
"username_pattern": "jf{{mail}}jfrog.com | $0"
// mail = "gitsaidegreen@jfrog.com"
// Pattern expects the email to start with "jf" and end with "jfrog.com"
// Match: fails because the claim does not meet the pattern
Github Actions Workflow Yaml
The below script determines whether to deploy to the development or release repository based on the branch that triggered the workflow.
build-publish.yml
name: "JFrog-GitHub NPM Publish OIDC Integration"
on:
workflow_dispatch:
# push:
# branches:
# - dev-FB-oidc-poc
# - main
permissions:
id-token: write
jobs:
build:
runs-on: self-hosted
env:
OIDC_AUDIENCE: 'frog-company'
OIDC_PROVIDER: 'jeremyl-frog'
RELEASE_REPO: 'jl-npm-releases' # Virtual that aggregates jl-npm-releases-local & npm-remote
DEV_REPO: 'jl-npm-dev' # Virtual that aggregates jl-npm-dev-local & npm-remote
JF_URL: ${{ secrets.CLOUD_JF_URL }}
BUILD_NAME: oidc-npm-publish-example
BUILD_NUMBER: ${{ github.run_number }}
defaults:
run:
working-directory: ./package
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node npm
uses: actions/setup-node@v3
- name: Setup JFrog CLI
uses: jfrog/setup-jfrog-cli@v4.5.6
with:
oidc-provider-name: ${{ env.OIDC_PROVIDER }}
oidc-audience: ${{ env.OIDC_AUDIENCE }}
env:
JF_URL: ${{ env.JF_URL }}
- name: Determine target repo
id: repo
run: |
if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then
echo "PLATFORM_REPO=${RELEASE_REPO}" >> $GITHUB_ENV
else
echo "PLATFORM_REPO=${DEV_REPO}" >> $GITHUB_ENV
fi
- name: Set CLI Config (dependency resolution only)
run: |
jf npm-config \
--repo-resolve=$PLATFORM_REPO \
--repo-deploy=$PLATFORM_REPO \
--server-id-resolve setup-jfrog-cli-server \
--server-id-deploy setup-jfrog-cli-server
- name: Show RT Servers and Set default
run: jf c use setup-jfrog-cli-server
- name: Install NPM dependencies
run: jf npm i --ignore-scripts --build-name ${{ env.BUILD_NAME }} --build-number ${{ env.BUILD_NUMBER }}
- name: Add Git data to Build Info
run: jf rt build-add-git ${{ env.BUILD_NAME }} ${{ env.BUILD_NUMBER }}
- name: Collect environment info
run: jf rt build-collect-env ${{ env.BUILD_NAME }} ${{ env.BUILD_NUMBER }}
- name: Capture committer and github actor name
run: |
COMMITTER=$(git log -1 --pretty=format:'%an' | tr ' ' '_')
echo "Committer: $COMMITTER"
echo "COMMITTER=$COMMITTER" >> $GITHUB_ENV
echo "GITHUB_ACTOR=${{ github.actor }}" >> $GITHUB_ENV
- name: NPM publish
run: jf npm publish --build-name ${{ env.BUILD_NAME }} --build-number ${{ env.BUILD_NUMBER }}
- name: Add github-commiter and github-actor properties
run: |
PACKAGE_NAME=$(jq -r .name package.json)
PACKAGE_VERSION=$(jq -r .version package.json)
# NPM repo format: <repo>/<package-name>/-/<package-name>-<version>.tgz
ARTIFACT_PATH="$PLATFORM_REPO/$PACKAGE_NAME/-/$PACKAGE_NAME-$PACKAGE_VERSION.tgz"
echo "Artifact Path: $ARTIFACT_PATH"
jf rt sp "$ARTIFACT_PATH" "github-commiter=${COMMITTER};github-actor=${GITHUB_ACTOR}"
- name: Publish Build Info to Artifactory
run: jf rt build-publish ${{ env.BUILD_NAME }} ${{ env.BUILD_NUMBER }}
# Alternative for other package types, jf rt upload (attaching properties on deployment) and build info steps
#- name: Publish with deployed-by property
# run: jf rt u <filename> ${{ env.PLATFORM_REPO }}/ --target-props=deployed-by=${{ env.COMMITTER }}
#- name: Publish Build info With JFrog CLI
# run: |
# jf rt build-collect-env
# jf rt build-add-git
# jf rt build-publish
# Pro Tip:
# Use ${{ ... }} only in YAML, when GitHub Actions needs to interpolate workflow or secret variables.
# Use $VAR inside run: blocks, after exporting anything to the environment.