From 4b8302b5722efb70ecde98d4c0e5701d855fa12e Mon Sep 17 00:00:00 2001 From: Steven Wong Date: Thu, 25 Jul 2024 22:25:20 +0800 Subject: [PATCH 01/18] Update codeowner file with new GitHub team name --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 60f116c..7958e8b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @auth0/dx-sdks-engineer +* @auth0/project-dx-sdks-engineer-codeowner From 8e2c478016515130fdc14e7fce521d7fcf9c09a9 Mon Sep 17 00:00:00 2001 From: arpit-jain_atko Date: Fri, 4 Oct 2024 13:34:14 +0530 Subject: [PATCH 02/18] Changed pull_request_target to pull_request --- .github/workflows/semgrep.yml | 11 +---------- .github/workflows/snyk.yml | 11 +---------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 36c687d..b0411b0 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -2,7 +2,7 @@ name: Semgrep on: merge_group: - pull_request_target: + pull_request: types: - opened - synchronize @@ -20,16 +20,7 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} jobs: - authorize: - name: Authorize - environment: ${{ github.actor != 'dependabot[bot]' && github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository && 'external' || 'internal' }} - runs-on: ubuntu-latest - steps: - - run: true - run: - needs: authorize # Require approval before running on forked pull requests - name: Check for Vulnerabilities runs-on: ubuntu-latest diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml index de751b8..da1f868 100644 --- a/.github/workflows/snyk.yml +++ b/.github/workflows/snyk.yml @@ -3,7 +3,7 @@ name: Snyk on: merge_group: workflow_dispatch: - pull_request_target: + pull_request: types: - opened - synchronize @@ -21,16 +21,7 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} jobs: - authorize: - name: Authorize - environment: ${{ github.actor != 'dependabot[bot]' && github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository && 'external' || 'internal' }} - runs-on: ubuntu-latest - steps: - - run: true - check: - needs: authorize - name: Check for Vulnerabilities runs-on: ubuntu-latest From 2e2e2488af0da877740aa187b4a3304f509b3b67 Mon Sep 17 00:00:00 2001 From: Snehil Kishore Date: Thu, 31 Oct 2024 13:47:32 +0530 Subject: [PATCH 03/18] Adding Reversing Lab Scanner (#198) --- .github/actions/get-prerelease/action.yml | 30 ++++++++ .github/actions/get-release-notes/action.yml | 42 +++++++++++ .github/actions/get-version/action.yml | 21 ++++++ .github/actions/release-create/action.yml | 47 +++++++++++++ .github/actions/rl-scanner/action.yml | 71 +++++++++++++++++++ .github/actions/rubygems-publish/action.yml | 30 ++++++++ .github/actions/tag-exists/action.yml | 36 ++++++++++ .github/workflows/publish.yml | 43 ++++++------ .github/workflows/rl-scanner.yml | 68 ++++++++++++++++++ .github/workflows/ruby-release.yml | 73 ++++++++++++++++++++ .shiprc | 3 +- .version | 1 + 12 files changed, 442 insertions(+), 23 deletions(-) create mode 100644 .github/actions/get-prerelease/action.yml create mode 100644 .github/actions/get-release-notes/action.yml create mode 100644 .github/actions/get-version/action.yml create mode 100644 .github/actions/release-create/action.yml create mode 100644 .github/actions/rl-scanner/action.yml create mode 100644 .github/actions/rubygems-publish/action.yml create mode 100644 .github/actions/tag-exists/action.yml create mode 100644 .github/workflows/rl-scanner.yml create mode 100644 .github/workflows/ruby-release.yml create mode 100644 .version diff --git a/.github/actions/get-prerelease/action.yml b/.github/actions/get-prerelease/action.yml new file mode 100644 index 0000000..131f93d --- /dev/null +++ b/.github/actions/get-prerelease/action.yml @@ -0,0 +1,30 @@ +name: Return a boolean indicating if the version contains prerelease identifiers + +# +# Returns a simple true/false boolean indicating whether the version indicates it's a prerelease or not. +# +# TODO: Remove once the common repo is public. +# + +inputs: + version: + required: true + +outputs: + prerelease: + value: ${{ steps.get_prerelease.outputs.PRERELEASE }} + +runs: + using: composite + + steps: + - id: get_prerelease + shell: bash + run: | + if [[ "${VERSION}" == *"beta"* || "${VERSION}" == *"alpha"* ]]; then + echo "PRERELEASE=true" >> $GITHUB_OUTPUT + else + echo "PRERELEASE=false" >> $GITHUB_OUTPUT + fi + env: + VERSION: ${{ inputs.version }} \ No newline at end of file diff --git a/.github/actions/get-release-notes/action.yml b/.github/actions/get-release-notes/action.yml new file mode 100644 index 0000000..5ce3f92 --- /dev/null +++ b/.github/actions/get-release-notes/action.yml @@ -0,0 +1,42 @@ +name: Return the release notes extracted from the PR body + +# +# Returns the release notes from the content of a pull request linked to a release branch. It expects the branch name to be in the format release/vX.Y.Z, release/X.Y.Z, release/vX.Y.Z-beta.N. etc. +# +# TODO: Remove once the common repo is public. +# +inputs: + version: + required: true + repo_name: + required: false + repo_owner: + required: true + token: + required: true + +outputs: + release-notes: + value: ${{ steps.get_release_notes.outputs.RELEASE_NOTES }} + +runs: + using: composite + + steps: + - uses: actions/github-script@v7 + id: get_release_notes + with: + result-encoding: string + script: | + const { data: pulls } = await github.rest.pulls.list({ + owner: process.env.REPO_OWNER, + repo: process.env.REPO_NAME, + state: 'all', + head: `${process.env.REPO_OWNER}:release/${process.env.VERSION}`, + }); + core.setOutput('RELEASE_NOTES', pulls[0].body); + env: + GITHUB_TOKEN: ${{ inputs.token }} + REPO_OWNER: ${{ inputs.repo_owner }} + REPO_NAME: ${{ inputs.repo_name }} + VERSION: ${{ inputs.version }} \ No newline at end of file diff --git a/.github/actions/get-version/action.yml b/.github/actions/get-version/action.yml new file mode 100644 index 0000000..84814a3 --- /dev/null +++ b/.github/actions/get-version/action.yml @@ -0,0 +1,21 @@ +name: Return the version extracted from the branch name + +# +# Returns the version from the .version file. +# +# TODO: Remove once the common repo is public. +# + +outputs: + version: + value: ${{ steps.get_version.outputs.VERSION }} + +runs: + using: composite + + steps: + - id: get_version + shell: bash + run: | + VERSION=$(head -1 .version) + echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT \ No newline at end of file diff --git a/.github/actions/release-create/action.yml b/.github/actions/release-create/action.yml new file mode 100644 index 0000000..a0db443 --- /dev/null +++ b/.github/actions/release-create/action.yml @@ -0,0 +1,47 @@ +name: Create a GitHub release + +# +# Creates a GitHub release with the given version. +# +# TODO: Remove once the common repo is public. +# + +inputs: + token: + required: true + files: + required: false + name: + required: true + body: + required: true + tag: + required: true + commit: + required: true + draft: + default: false + required: false + prerelease: + default: false + required: false + fail_on_unmatched_files: + default: true + required: false + +runs: + using: composite + + steps: + - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 + with: + body: ${{ inputs.body }} + name: ${{ inputs.name }} + tag_name: ${{ inputs.tag }} + target_commitish: ${{ inputs.commit }} + draft: ${{ inputs.draft }} + prerelease: ${{ inputs.prerelease }} + fail_on_unmatched_files: ${{ inputs.fail_on_unmatched_files }} + files: ${{ inputs.files }} + env: + GITHUB_TOKEN: ${{ inputs.token }} \ No newline at end of file diff --git a/.github/actions/rl-scanner/action.yml b/.github/actions/rl-scanner/action.yml new file mode 100644 index 0000000..b3df2d9 --- /dev/null +++ b/.github/actions/rl-scanner/action.yml @@ -0,0 +1,71 @@ +name: 'Reversing Labs Scanner' +description: 'Runs the Reversing Labs scanner on a specified artifact.' +inputs: + artifact-path: + description: 'Path to the artifact to be scanned.' + required: true + version: + description: 'Version of the artifact.' + required: true + +runs: + using: 'composite' + steps: + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install Python dependencies + shell: bash + run: | + pip install boto3 requests + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + role-to-assume: ${{ env.PRODSEC_TOOLS_ARN }} + aws-region: us-east-1 + mask-aws-account-id: true + + - name: Install RL Wrapper + shell: bash + run: | + pip install rl-wrapper>=1.0.0 --index-url "https://${{ env.PRODSEC_TOOLS_USER }}:${{ env.PRODSEC_TOOLS_TOKEN }}@a0us.jfrog.io/artifactory/api/pypi/python-local/simple" + + - name: Run RL Scanner + shell: bash + env: + RLSECURE_LICENSE: ${{ env.RLSECURE_LICENSE }} + RLSECURE_SITE_KEY: ${{ env.RLSECURE_SITE_KEY }} + SIGNAL_HANDLER_TOKEN: ${{ env.SIGNAL_HANDLER_TOKEN }} + PYTHONUNBUFFERED: 1 + run: | + if [ ! -f "${{ inputs.artifact-path }}" ]; then + echo "Artifact not found: ${{ inputs.artifact-path }}" + exit 1 + fi + + rl-wrapper \ + --artifact "${{ inputs.artifact-path }}" \ + --name "${{ github.event.repository.name }}" \ + --version "${{ inputs.version }}" \ + --repository "${{ github.repository }}" \ + --commit "${{ github.sha }}" \ + --build-env "github_actions" \ + --suppress_output + + # Check the outcome of the scanner + if [ $? -ne 0 ]; then + echo "RL Scanner failed." + echo "scan-status=failed" >> $GITHUB_ENV + exit 1 + else + echo "RL Scanner passed." + echo "scan-status=success" >> $GITHUB_ENV + fi + +outputs: + scan-status: + description: 'The outcome of the scan process.' + value: ${{ env.scan-status }} \ No newline at end of file diff --git a/.github/actions/rubygems-publish/action.yml b/.github/actions/rubygems-publish/action.yml new file mode 100644 index 0000000..e7ea970 --- /dev/null +++ b/.github/actions/rubygems-publish/action.yml @@ -0,0 +1,30 @@ +name: Publishes to RubyGems + +# +# Publishes to RubyGems +# +# TODO: Remove once the common repo is public. +# + +inputs: + rubygems-token: + required: true + ruby-version: + required: true + +runs: + using: composite + + steps: + - name: Configure Ruby + uses: ./.github/actions/setup + with: + ruby: ${{ inputs.ruby-version }} + + - name: Publish to RubyGems + shell: bash + run: | + gem build *.gemspec + gem push *.gem + env: + GEM_HOST_API_KEY: ${{ inputs.rubygems-token }} \ No newline at end of file diff --git a/.github/actions/tag-exists/action.yml b/.github/actions/tag-exists/action.yml new file mode 100644 index 0000000..b8f33f6 --- /dev/null +++ b/.github/actions/tag-exists/action.yml @@ -0,0 +1,36 @@ +name: Return a boolean indicating if a tag already exists for the repository + +# +# Returns a simple true/false boolean indicating whether the tag exists or not. +# +# TODO: Remove once the common repo is public. +# + +inputs: + token: + required: true + tag: + required: true + +outputs: + exists: + description: 'Whether the tag exists or not' + value: ${{ steps.tag-exists.outputs.EXISTS }} + +runs: + using: composite + + steps: + - id: tag-exists + shell: bash + run: | + GET_API_URL="https://api.github.com/repos/${GITHUB_REPOSITORY}/git/ref/tags/${TAG_NAME}" + http_status_code=$(curl -LI $GET_API_URL -o /dev/null -w '%{http_code}\n' -s -H "Authorization: token ${GITHUB_TOKEN}") + if [ "$http_status_code" -ne "404" ] ; then + echo "EXISTS=true" >> $GITHUB_OUTPUT + else + echo "EXISTS=false" >> $GITHUB_OUTPUT + fi + env: + TAG_NAME: ${{ inputs.tag }} + GITHUB_TOKEN: ${{ inputs.token }} \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index da2a4ce..00b6fbe 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -8,30 +8,29 @@ on: required: true default: master + permissions: contents: read + id-token: write # This is required for requesting the JWT jobs: - publish: - name: Publish to RubyGems - runs-on: ubuntu-latest - environment: release - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.event.inputs.branch }} + rl-scanner: + uses: ./.github/workflows/rl-scanner.yml + with: + ruby-version: 3.2 + secrets: + RLSECURE_LICENSE: ${{ secrets.RLSECURE_LICENSE }} + RLSECURE_SITE_KEY: ${{ secrets.RLSECURE_SITE_KEY }} + SIGNAL_HANDLER_TOKEN: ${{ secrets.SIGNAL_HANDLER_TOKEN }} + PRODSEC_TOOLS_USER: ${{ secrets.PRODSEC_TOOLS_USER }} + PRODSEC_TOOLS_TOKEN: ${{ secrets.PRODSEC_TOOLS_TOKEN }} + PRODSEC_TOOLS_ARN: ${{ secrets.PRODSEC_TOOLS_ARN }} - - name: Configure Ruby - uses: ./.github/actions/setup - with: - ruby: 3.2 - - - name: Publish to RubyGems - run: | - gem build *.gemspec - gem push *.gem - env: - GEM_HOST_API_KEY: ${{secrets.RUBYGEMS_AUTH_TOKEN}} + publish: + uses: ./.github/workflows/ruby-release.yml + needs: rl-scanner + with: + ruby-version: 3.2 + secrets: + github-token: ${{ secrets.GITHUB_TOKEN }} + rubygems-token: ${{ secrets.RUBYGEMS_AUTH_TOKEN }} diff --git a/.github/workflows/rl-scanner.yml b/.github/workflows/rl-scanner.yml new file mode 100644 index 0000000..627ae08 --- /dev/null +++ b/.github/workflows/rl-scanner.yml @@ -0,0 +1,68 @@ +name: RL-Secure Workflow + +on: + workflow_call: + inputs: + ruby-version: + required: true + type: string + secrets: + RLSECURE_LICENSE: + required: true + RLSECURE_SITE_KEY: + required: true + SIGNAL_HANDLER_TOKEN: + required: true + PRODSEC_TOOLS_USER: + required: true + PRODSEC_TOOLS_TOKEN: + required: true + PRODSEC_TOOLS_ARN: + required: true + +jobs: + rl-scanner: + if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.merged && startsWith(github.event.pull_request.head.ref, 'release/')) + runs-on: ubuntu-latest + outputs: + scan-status: ${{ steps.rl-scan-conclusion.outcome }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event.inputs.branch }} + + - name: Configure Ruby + uses: ./.github/actions/setup + with: + ruby-version: ${{ inputs.ruby-version }} + + - name: Build RubyGems + shell: bash + run: | + gem build *.gemspec + export GEM_FILE=$(ls *.gem) + echo "gem_file=$GEM_FILE" >> $GITHUB_ENV + + - name: Get Artifact Version + id: get_version + uses: ./.github/actions/get-version + + - name: Run RL Scanner + id: rl-scan-conclusion + uses: ./.github/actions/rl-scanner + with: + artifact-path: "$(pwd)/${{ env.gem_file }}" + version: "${{ steps.get_version.outputs.version }}" + env: + RLSECURE_LICENSE: ${{ secrets.RLSECURE_LICENSE }} + RLSECURE_SITE_KEY: ${{ secrets.RLSECURE_SITE_KEY }} + SIGNAL_HANDLER_TOKEN: ${{ secrets.SIGNAL_HANDLER_TOKEN }} + PRODSEC_TOOLS_USER: ${{ secrets.PRODSEC_TOOLS_USER }} + PRODSEC_TOOLS_TOKEN: ${{ secrets.PRODSEC_TOOLS_TOKEN }} + PRODSEC_TOOLS_ARN: ${{ secrets.PRODSEC_TOOLS_ARN }} + + - name: Output scan result + run: echo "scan-status=${{ steps.rl-scan-conclusion.outcome }}" >> $GITHUB_ENV \ No newline at end of file diff --git a/.github/workflows/ruby-release.yml b/.github/workflows/ruby-release.yml new file mode 100644 index 0000000..702a910 --- /dev/null +++ b/.github/workflows/ruby-release.yml @@ -0,0 +1,73 @@ +name: Create Release + +on: + workflow_call: + inputs: + ruby-version: + required: true + type: string + secrets: + github-token: + required: true + rubygems-token: + required: true + +jobs: + release: + if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.merged && startsWith(github.event.pull_request.head.ref, 'release/')) + runs-on: ubuntu-latest + environment: release + + steps: + # Checkout the code + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event.inputs.branch }} + + # Get the version from the branch name + - id: get_version + uses: ./.github/actions/get-version + + # Get the prerelease flag from the branch name + - id: get_prerelease + uses: ./.github/actions/get-prerelease + with: + version: ${{ steps.get_version.outputs.version }} + + # Get the release notes + # This will expose the release notes as env.RELEASE_NOTES + - id: get_release_notes + uses: ./.github/actions/get-release-notes + with: + token: ${{ secrets.github-token }} + version: ${{ steps.get_version.outputs.version }} + repo_owner: ${{ github.repository_owner }} + repo_name: ${{ github.event.repository.name }} + + # Check if the tag already exists + - id: tag_exists + uses: ./.github/actions/tag-exists + with: + tag: ${{ steps.get_version.outputs.version }} + token: ${{ secrets.github-token }} + + # If the tag already exists, exit with an error + - if: steps.tag_exists.outputs.exists == 'true' + run: exit 1 + + # Publish the release to our package manager + - uses: ./.github/actions/rubygems-publish + with: + ruby-version: ${{ inputs.ruby-version }} + rubygems-token: ${{ secrets.rubygems-token }} + + # Create a release for the tag + - uses: ./.github/actions/release-create + with: + token: ${{ secrets.github-token }} + name: ${{ steps.get_version.outputs.version }} + body: ${{ steps.get_release_notes.outputs.release-notes }} + tag: ${{ steps.get_version.outputs.version }} + commit: ${{ github.sha }} + prerelease: ${{ steps.get_prerelease.outputs.prerelease }} diff --git a/.shiprc b/.shiprc index e531e09..c1c10f9 100644 --- a/.shiprc +++ b/.shiprc @@ -1,6 +1,7 @@ { "files": { - "lib/omniauth-auth0/version.rb": [] + "lib/omniauth-auth0/version.rb": [], + ".version": [] }, "prebump": "bundle install && bundle exec rake test", "postbump": "bundle update" diff --git a/.version b/.version new file mode 100644 index 0000000..50e47c8 --- /dev/null +++ b/.version @@ -0,0 +1 @@ +3.1.1 \ No newline at end of file From e218e0ce2935529ec6c82f7f50d89bfb959fec19 Mon Sep 17 00:00:00 2001 From: Eduardo Boronat Date: Wed, 23 Jul 2025 10:46:43 +0000 Subject: [PATCH 04/18] chore(ci): Remove Semgrep GHA Workflow --- .github/workflows/semgrep.yml | 40 ----------------------------------- 1 file changed, 40 deletions(-) delete mode 100644 .github/workflows/semgrep.yml diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml deleted file mode 100644 index b0411b0..0000000 --- a/.github/workflows/semgrep.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Semgrep - -on: - merge_group: - pull_request: - types: - - opened - - synchronize - push: - branches: - - master - schedule: - - cron: "30 0 1,15 * *" - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} - -jobs: - run: - name: Check for Vulnerabilities - runs-on: ubuntu-latest - - container: - image: returntocorp/semgrep - - steps: - - if: github.actor == 'dependabot[bot]' || github.event_name == 'merge_group' - run: exit 0 # Skip unnecessary test runs for dependabot and merge queues. Artifically flag as successful, as this is a required check for branch protection. - - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha || github.ref }} - - - run: semgrep ci - env: - SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} From 42cf1adcc750d54acaed560bb794c8ea1d05a28f Mon Sep 17 00:00:00 2001 From: arpit-jain_atko Date: Mon, 15 Sep 2025 14:56:37 +0530 Subject: [PATCH 05/18] chore: Bump rexml from 3.2.5 to 3.4.4 --- Gemfile.lock | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index e5f41f5..55eee6d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -91,7 +91,7 @@ GEM rb-inotify (0.10.1) ffi (~> 1.0) regexp_parser (2.8.1) - rexml (3.2.5) + rexml (3.4.4) rspec (3.12.0) rspec-core (~> 3.12.0) rspec-expectations (~> 3.12.0) @@ -157,6 +157,7 @@ PLATFORMS aarch64-linux arm64-darwin-21 arm64-darwin-22 + arm64-darwin-23 x86_64-darwin-22 x86_64-linux From 0c7cfec5200ae50d5e04b51c18231bcdbb7ff681 Mon Sep 17 00:00:00 2001 From: arpit-jain_atko Date: Mon, 15 Sep 2025 15:44:52 +0530 Subject: [PATCH 06/18] chore: Bump rexml from 3.2.5 to 3.3.9 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 55eee6d..9250636 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -91,7 +91,7 @@ GEM rb-inotify (0.10.1) ffi (~> 1.0) regexp_parser (2.8.1) - rexml (3.4.4) + rexml (3.3.9) rspec (3.12.0) rspec-core (~> 3.12.0) rspec-expectations (~> 3.12.0) From 2e1a04666139a23ab14adfd10dec79375872f5b6 Mon Sep 17 00:00:00 2001 From: arpit-jain_atko Date: Fri, 17 Oct 2025 12:22:47 +0530 Subject: [PATCH 07/18] docs: Add Ask DeepWiki badge --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 88d4104..4235726 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ [![codecov](https://codecov.io/gh/auth0/omniauth-auth0/branch/master/graph/badge.svg)](https://codecov.io/gh/auth0/omniauth-auth0) [![Gem Version](https://badge.fury.io/rb/omniauth-auth0.svg)](https://badge.fury.io/rb/omniauth-auth0) [![MIT licensed](https://img.shields.io/dub/l/vibe-d.svg?style=flat)](https://github.com/auth0/omniauth-auth0/blob/master/LICENSE) +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/auth0/omniauth-auth0)
📚 Documentation - 🚀 Getting started - 💻 API reference - 💬 Feedback @@ -165,4 +166,4 @@ Please do not report security vulnerabilities on the public GitHub issue tracker

This project is licensed under the MIT license. See the LICENSE file for more info. -

+

\ No newline at end of file From c359e6f2778af86082f6934a9dcfddf6e6591801 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 22:26:25 +0000 Subject: [PATCH 08/18] Bump faraday from 2.7.10 to 2.14.1 Bumps [faraday](https://github.com/lostisland/faraday) from 2.7.10 to 2.14.1. - [Release notes](https://github.com/lostisland/faraday/releases) - [Changelog](https://github.com/lostisland/faraday/blob/main/CHANGELOG.md) - [Commits](https://github.com/lostisland/faraday/compare/v2.7.10...v2.14.1) --- updated-dependencies: - dependency-name: faraday dependency-version: 2.14.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 9250636..e0099f5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -19,10 +19,12 @@ GEM docile (1.4.0) dotenv (2.8.1) eventmachine (1.2.7) - faraday (2.7.10) - faraday-net_http (>= 2.0, < 3.1) - ruby2_keywords (>= 0.0.4) - faraday-net_http (3.0.2) + faraday (2.14.1) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-net_http (3.4.2) + net-http (~> 0.5) ffi (1.15.5) formatador (1.1.0) guard (2.18.0) @@ -47,6 +49,7 @@ GEM listen (3.8.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) lumberjack (1.2.8) method_source (1.0.0) multi_json (1.15.0) @@ -54,6 +57,8 @@ GEM mustermann (3.0.0) ruby2_keywords (~> 0.0.1) nenv (0.3.0) + net-http (0.9.1) + uri (>= 0.11.1) notiffany (0.1.3) nenv (~> 0.1) shellany (~> 0.0) @@ -147,6 +152,7 @@ GEM thor (1.2.2) tilt (2.2.0) unicode-display_width (2.4.2) + uri (1.1.1) version_gem (1.1.3) webmock (3.18.1) addressable (>= 2.8.0) From 1facf5a8f5f289d4dd5c526842fc258dbeab61e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 22:20:43 +0000 Subject: [PATCH 09/18] Bump rack from 2.2.7 to 2.2.23 Bumps [rack](https://github.com/rack/rack) from 2.2.7 to 2.2.23. - [Release notes](https://github.com/rack/rack/releases) - [Changelog](https://github.com/rack/rack/blob/main/CHANGELOG.md) - [Commits](https://github.com/rack/rack/compare/v2.2.7...v2.2.23) --- updated-dependencies: - dependency-name: rack dependency-version: 2.2.23 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 9250636..8d0e106 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -80,7 +80,7 @@ GEM method_source (~> 1.0) public_suffix (5.0.3) racc (1.7.1) - rack (2.2.7) + rack (2.2.23) rack-protection (3.0.6) rack rack-test (2.1.0) From 2b5d9ecd0b914c408af2bd62ca7619b110d41f7c Mon Sep 17 00:00:00 2001 From: David Kaczowka Date: Fri, 20 Jun 2025 14:00:07 -0500 Subject: [PATCH 10/18] Add support for client assertion signing key authentication while continuing to support client secret authentication. The client assertion signing key allows Private Key JWT application authentication in Auth0. The client_assertion_signing_key and client_assertion_signing_algorithm are optional parameters. Client assercition signing key will be used if a client_assertion_signing_key is privided. The client_assertion_signing_algorithm is optional. The algorithm defaults to RS256 if nil or not provided. --- Gemfile.lock | 1 + README.md | 40 ++ lib/omniauth/auth0/jwt_token.rb | 38 ++ lib/omniauth/auth0/jwt_validator.rb | 4 +- lib/omniauth/strategies/auth0.rb | 62 ++- omniauth-auth0.gemspec | 1 + spec/omniauth/auth0/jwt_token_spec.rb | 87 +++ spec/omniauth/strategies/auth0_spec.rb | 707 +++++++++++++++++-------- 8 files changed, 694 insertions(+), 246 deletions(-) create mode 100644 lib/omniauth/auth0/jwt_token.rb create mode 100644 spec/omniauth/auth0/jwt_token_spec.rb diff --git a/Gemfile.lock b/Gemfile.lock index 9250636..aae46c7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,6 +2,7 @@ PATH remote: . specs: omniauth-auth0 (3.1.1) + jwt (~> 2) omniauth (~> 2) omniauth-oauth2 (~> 1) diff --git a/README.md b/README.md index 4235726..b286c22 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,8 @@ Adding the SDK to your Rails app requires a few steps: Create the file `./config/auth0.yml` within your application directory with the following content: +### For client secret authentication + ```yml development: auth0_domain: @@ -61,10 +63,25 @@ development: auth0_client_secret: ``` +#### For client assertion signing key authentication + +```yml +development: + auth0_domain: + auth0_client_id: + auth0_client_assertion_signing_key: + auth0_client_assertion_signing_algorithm: +``` +**Note**: you must upload the corresponding public key to your Auth0 tenant, so that Auth0 is able to verify the JWT signature. + +client_assertion_signing_algorithm is optional and defaults to RS256. + ### Create the initializer Create a new Ruby file in `./config/initializers/auth0.rb` to configure the OmniAuth middleware: +### For client secret authentication + ```ruby AUTH0_CONFIG = Rails.application.config_for(:auth0) @@ -82,6 +99,29 @@ Rails.application.config.middleware.use OmniAuth::Builder do end ``` +#### For client assertion signing key authentication + +```ruby +AUTH0_CONFIG = Rails.application.config_for(:auth0) + +Rails.application.config.middleware.use OmniAuth::Builder do + provider( + :auth0, + AUTH0_CONFIG['auth0_client_id'], + nil, + AUTH0_CONFIG['auth0_domain'], + callback_path: '/auth/auth0/callback', + authorize_params: { + scope: 'openid profile' + }, + client_assertion_signing_key: OpenSSL::PKey::RSA.new(AUTH0_CONFIG[:auth0_client_assertion_signing_key]), + client_assertion_signing_algorithm: AUTH0_CONFIG[:auth0_client_assertion_signing_algorithm] + ) +end +``` + +**Note**: The client_assertion_signing_key must be provided as a PKey object. + ### Create the callback controller Create a new controller `./app/controllers/auth0_controller.rb` to handle the callback from Auth0. diff --git a/lib/omniauth/auth0/jwt_token.rb b/lib/omniauth/auth0/jwt_token.rb new file mode 100644 index 0000000..62a1e16 --- /dev/null +++ b/lib/omniauth/auth0/jwt_token.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'jwt' +require 'securerandom' + +module OmniAuth + module Auth0 + # JWTToken class to generate a JWT token for client assertion + # as per the OAuth 2.0 Client Credentials Grant specification. + class JWTToken + attr_reader :client_id, :domain_url, :client_assertion_signing_key, :client_assertion_signing_algorithm + + def initialize(client_id, domain_url, client_assertion_signing_key, client_assertion_signing_algorithm = nil) + @client_id = client_id + @domain_url = domain_url + @client_assertion_signing_key = client_assertion_signing_key + @client_assertion_signing_algorithm = client_assertion_signing_algorithm || 'RS256' + end + + def jwt_token + JWT.encode(jwt_payload, client_assertion_signing_key, client_assertion_signing_algorithm) + end + + private + + def jwt_payload + { + iss: client_id, + sub: client_id, + aud: File.join(domain_url, '/oauth/token'), + iat: Time.now.utc.to_i, + exp: Time.now.utc.to_i + 60, + jti: SecureRandom.uuid + } + end + end + end +end diff --git a/lib/omniauth/auth0/jwt_validator.rb b/lib/omniauth/auth0/jwt_validator.rb index d7f0ecf..cb99bfe 100644 --- a/lib/omniauth/auth0/jwt_validator.rb +++ b/lib/omniauth/auth0/jwt_validator.rb @@ -272,10 +272,10 @@ def verify_org(id_token, organization) if validate_as_id org_id = id_token['org_id'] if !org_id || !org_id.is_a?(String) - raise OmniAuth::Auth0::TokenValidationError, + raise OmniAuth::Auth0::TokenValidationError, 'Organization Id (org_id) claim must be a string present in the ID token' elsif org_id != organization - raise OmniAuth::Auth0::TokenValidationError, + raise OmniAuth::Auth0::TokenValidationError, "Organization Id (org_id) claim value mismatch in the ID token; expected '#{organization}', found '#{org_id}'" end else diff --git a/lib/omniauth/strategies/auth0.rb b/lib/omniauth/strategies/auth0.rb index dd86ab5..89c7d97 100644 --- a/lib/omniauth/strategies/auth0.rb +++ b/lib/omniauth/strategies/auth0.rb @@ -4,6 +4,7 @@ require 'uri' require 'securerandom' require 'omniauth-oauth2' +require 'omniauth/auth0/jwt_token' require 'omniauth/auth0/jwt_validator' require 'omniauth/auth0/telemetry' require 'omniauth/auth0/errors' @@ -13,6 +14,8 @@ module Strategies # Auth0 OmniAuth strategy class Auth0 < OmniAuth::Strategies::OAuth2 include OmniAuth::Auth0::Telemetry + AUTHORIZATION_CODE_GRANT_TYPE = 'authorization_code' + CLIENT_ASSERTION_TYPE = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' option :name, 'auth0' @@ -28,6 +31,8 @@ def client options.client_options.authorize_url = '/authorize' options.client_options.token_url = '/oauth/token' options.client_options.userinfo_url = '/userinfo' + setup_client_options_auth_scheme + super end @@ -100,25 +105,20 @@ def authorize_params end def build_access_token + options.token_params.merge!(client_assertion_signing_key_token_params) if client_assertion_signing_key_auth? options.token_params[:headers] = { 'Auth0-Client' => telemetry_encoded } super end # Declarative override for the request phase of authentication def request_phase - if no_client_id? - # Do we have a client_id for this Application? - fail!(:missing_client_id) - elsif no_client_secret? - # Do we have a client_secret for this Application? - fail!(:missing_client_secret) - elsif no_domain? - # Do we have a domain for this Application? - fail!(:missing_domain) - else - # All checks pass, run the Oauth2 request_phase method. - super - end + return fail!(:missing_client_id) if no_client_id? + return fail!(:missing_client_secret) if no_client_secret? + return fail!(:missing_domain) if no_domain? + return fail!(:missing_client_assertion_signing_key) if no_client_assertion_signing_key? + + # All checks pass, run the Oauth2 request_phase method. + super end def callback_phase @@ -128,10 +128,32 @@ def callback_phase end private + + def client_assertion_signing_key_auth? + options['client_assertion_signing_key'] + end + + def client_assertion_signing_key_token_params + { + grant_type: AUTHORIZATION_CODE_GRANT_TYPE, + client_id: options.client_id, + client_assertion_type: CLIENT_ASSERTION_TYPE, + client_assertion: jwt_token + } + end + def jwt_validator @jwt_validator ||= OmniAuth::Auth0::JWTValidator.new(options) end + def jwt_token + OmniAuth::Auth0::JWTToken.new(options.client_id, + domain_url, + options.client_assertion_signing_key, + options.client_assertion_signing_algorithm) + .jwt_token + end + # Parse the raw user info. def raw_info return @raw_info if @raw_info @@ -154,7 +176,7 @@ def no_client_id? # Check if the options include a client_secret def no_client_secret? - ['', nil].include?(options.client_secret) + ['', nil].include?(options.client_secret) && !options.key?('client_assertion_signing_key') end # Check if the options include a domain @@ -162,12 +184,24 @@ def no_domain? ['', nil].include?(options.domain) end + # Check if the options include a client_assertion_signing_key + def no_client_assertion_signing_key? + options.key?('client_assertion_signing_key') && ['', nil].include?(options.client_assertion_signing_key) + end + # Normalize a domain to a URL. def domain_url domain_url = URI(options.domain) domain_url = URI("https://#{domain_url}") if domain_url.scheme.nil? domain_url.to_s end + + # Setup the auth_scheme for the client options if using client assertion signing key + def setup_client_options_auth_scheme + return unless client_assertion_signing_key_auth? + + options.client_options.auth_scheme = :request_body + end end end end diff --git a/omniauth-auth0.gemspec b/omniauth-auth0.gemspec index 68b8102..f4fc6fb 100644 --- a/omniauth-auth0.gemspec +++ b/omniauth-auth0.gemspec @@ -21,6 +21,7 @@ omniauth-auth0 is the OmniAuth strategy for Auth0. s.executables = `git ls-files -- bin/*`.split('\n').map{ |f| File.basename(f) } s.require_paths = ['lib'] + s.add_runtime_dependency 'jwt', '~> 2' s.add_runtime_dependency 'omniauth', '~> 2' s.add_runtime_dependency 'omniauth-oauth2', '~> 1' diff --git a/spec/omniauth/auth0/jwt_token_spec.rb b/spec/omniauth/auth0/jwt_token_spec.rb new file mode 100644 index 0000000..ada4d32 --- /dev/null +++ b/spec/omniauth/auth0/jwt_token_spec.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'json' +require 'jwt' + +describe OmniAuth::Auth0::JWTToken do + let(:client_id) { 'CLIENT_ID' } + let(:domain_url) { 'https://samples.auth0.com' } + let(:client_assertion_signing_key) { OpenSSL::PKey::RSA.generate(2048) } + + describe '#jwt_token' do + it 'generates a valid JWT token' do + uuid = '12345678-1234-5678-1234-567812345678' + allow(SecureRandom).to receive(:uuid).and_return(uuid) + + jwt_token = described_class.new(client_id, + domain_url, + client_assertion_signing_key, + 'RS256') + .jwt_token + decoded_token = JWT.decode(jwt_token, client_assertion_signing_key, true, { algorithm: 'RS256' }) + + expect(decoded_token[0]['iss']).to eq(client_id) + expect(decoded_token[0]['sub']).to eq(client_id) + expect(decoded_token[0]['aud']).to eq("#{domain_url}/oauth/token") + expect(decoded_token[0]['iat']).to be_within(5).of(Time.now.utc.to_i) + expect(decoded_token[0]['exp']).to eq(decoded_token[0]['iat'] + 60) + expect(decoded_token[0]['jti']).to eq(uuid) + end + + it 'defaults to RS256 algorithm if not specified' do + uuid = '12345678-1234-5678-1234-567812345678' + allow(SecureRandom).to receive(:uuid).and_return(uuid) + + jwt_token = described_class.new(client_id, domain_url, client_assertion_signing_key).jwt_token + decoded_token = JWT.decode(jwt_token, client_assertion_signing_key, true, { algorithm: 'RS256' }) + + expect(decoded_token[0]['iss']).to eq(client_id) + expect(decoded_token[0]['sub']).to eq(client_id) + expect(decoded_token[0]['aud']).to eq("#{domain_url}/oauth/token") + expect(decoded_token[0]['iat']).to be_within(5).of(Time.now.utc.to_i) + expect(decoded_token[0]['exp']).to eq(decoded_token[0]['iat'] + 60) + expect(decoded_token[0]['jti']).to eq(uuid) + end + + context 'when using ES256 algorithm' do + let(:client_assertion_signing_key) { OpenSSL::PKey::EC.generate('prime256v1') } + + it 'generates a valid JWT token' do + uuid = '12345678-1234-5678-1234-567812345678' + allow(SecureRandom).to receive(:uuid).and_return(uuid) + jwt_token = described_class.new(client_id, + domain_url, + client_assertion_signing_key, + 'ES256') + .jwt_token + decoded_token = JWT.decode(jwt_token, client_assertion_signing_key, true, { algorithm: 'ES256' }) + + expect(decoded_token[0]['iss']).to eq(client_id) + expect(decoded_token[0]['sub']).to eq(client_id) + expect(decoded_token[0]['aud']).to eq("#{domain_url}/oauth/token") + expect(decoded_token[0]['iat']).to be_within(5).of(Time.now.utc.to_i) + expect(decoded_token[0]['exp']).to eq(decoded_token[0]['iat'] + 60) + expect(decoded_token[0]['jti']).to eq(uuid) + end + + it 'accepts client_assertion_signing_key_token_params as a string' do + uuid = '12345678-1234-5678-1234-567812345678' + allow(SecureRandom).to receive(:uuid).and_return(uuid) + jwt_token = described_class.new(client_id, + domain_url, + client_assertion_signing_key, + 'ES256') + .jwt_token + decoded_token = JWT.decode(jwt_token, client_assertion_signing_key, true, { algorithm: 'ES256' }) + + expect(decoded_token[0]['iss']).to eq(client_id) + expect(decoded_token[0]['sub']).to eq(client_id) + expect(decoded_token[0]['aud']).to eq("#{domain_url}/oauth/token") + expect(decoded_token[0]['iat']).to be_within(5).of(Time.now.utc.to_i) + expect(decoded_token[0]['exp']).to eq(decoded_token[0]['iat'] + 60) + expect(decoded_token[0]['jti']).to eq(uuid) + end + end + end +end diff --git a/spec/omniauth/strategies/auth0_spec.rb b/spec/omniauth/strategies/auth0_spec.rb index ec91017..4d47adf 100644 --- a/spec/omniauth/strategies/auth0_spec.rb +++ b/spec/omniauth/strategies/auth0_spec.rb @@ -10,10 +10,231 @@ it { expect(subject.site).to eq(url) } end +RSpec.shared_examples 'client_options with valid configuration' do + context 'domain with https' do + let(:domain_url) { 'https://samples.auth0.com' } + it_behaves_like 'site has valid domain url', 'https://samples.auth0.com' + end + + context 'domain with http' do + let(:domain_url) { 'http://mydomain.com' } + it_behaves_like 'site has valid domain url', 'http://mydomain.com' + end + + context 'domain with host only' do + let(:domain_url) { 'samples.auth0.com' } + it_behaves_like 'site has valid domain url', 'https://samples.auth0.com' + end + + it 'should have correct authorize path' do + expect(subject.options[:authorize_url]).to eq('/authorize') + end + + it 'should have the correct userinfo path' do + expect(subject.options[:userinfo_url]).to eq('/userinfo') + end + + it 'should have the correct token path' do + expect(subject.options[:token_url]).to eq('/oauth/token') + end +end + +RSpec.shared_examples 'oauth redirects with various parameters' do + it 'redirects to hosted login page' do + get 'auth/auth0' + expect(last_response.status).to eq(302) + redirect_url = last_response.headers['Location'] + expect(redirect_url).to start_with('https://samples.auth0.com/authorize') + expect(redirect_url).to have_query('response_type', 'code') + expect(redirect_url).to have_query('state') + expect(redirect_url).to have_query('client_id') + expect(redirect_url).to have_query('redirect_uri') + expect(redirect_url).not_to have_query('auth0Client') + expect(redirect_url).not_to have_query('connection') + expect(redirect_url).not_to have_query('connection_scope') + expect(redirect_url).not_to have_query('prompt') + expect(redirect_url).not_to have_query('screen_hint') + expect(redirect_url).not_to have_query('login_hint') + expect(redirect_url).not_to have_query('organization') + expect(redirect_url).not_to have_query('invitation') + end + + it 'redirects to hosted login page' do + get 'auth/auth0?connection=abcd' + expect(last_response.status).to eq(302) + redirect_url = last_response.headers['Location'] + expect(redirect_url).to start_with('https://samples.auth0.com/authorize') + expect(redirect_url).to have_query('response_type', 'code') + expect(redirect_url).to have_query('state') + expect(redirect_url).to have_query('client_id') + expect(redirect_url).to have_query('redirect_uri') + expect(redirect_url).to have_query('connection', 'abcd') + expect(redirect_url).not_to have_query('auth0Client') + expect(redirect_url).not_to have_query('connection_scope') + expect(redirect_url).not_to have_query('prompt') + expect(redirect_url).not_to have_query('screen_hint') + expect(redirect_url).not_to have_query('login_hint') + expect(redirect_url).not_to have_query('organization') + expect(redirect_url).not_to have_query('invitation') + end + + it 'redirects to the hosted login page with connection_scope' do + get 'auth/auth0?connection_scope=identity_provider_scope' + expect(last_response.status).to eq(302) + redirect_url = last_response.headers['Location'] + expect(redirect_url).to start_with('https://samples.auth0.com/authorize') + expect(redirect_url) + .to have_query('connection_scope', 'identity_provider_scope') + end + + it 'redirects to hosted login page with prompt=login' do + get 'auth/auth0?prompt=login' + expect(last_response.status).to eq(302) + redirect_url = last_response.headers['Location'] + expect(redirect_url).to start_with('https://samples.auth0.com/authorize') + expect(redirect_url).to have_query('response_type', 'code') + expect(redirect_url).to have_query('state') + expect(redirect_url).to have_query('client_id') + expect(redirect_url).to have_query('redirect_uri') + expect(redirect_url).to have_query('prompt', 'login') + expect(redirect_url).not_to have_query('auth0Client') + expect(redirect_url).not_to have_query('connection') + expect(redirect_url).not_to have_query('login_hint') + expect(redirect_url).not_to have_query('organization') + expect(redirect_url).not_to have_query('invitation') + end + + it 'redirects to hosted login page with screen_hint=signup' do + get 'auth/auth0?screen_hint=signup' + expect(last_response.status).to eq(302) + redirect_url = last_response.headers['Location'] + expect(redirect_url).to start_with('https://samples.auth0.com/authorize') + expect(redirect_url).to have_query('response_type', 'code') + expect(redirect_url).to have_query('state') + expect(redirect_url).to have_query('client_id') + expect(redirect_url).to have_query('redirect_uri') + expect(redirect_url).to have_query('screen_hint', 'signup') + expect(redirect_url).not_to have_query('auth0Client') + expect(redirect_url).not_to have_query('connection') + expect(redirect_url).not_to have_query('login_hint') + expect(redirect_url).not_to have_query('organization') + expect(redirect_url).not_to have_query('invitation') + end + + it 'redirects to hosted login page with organization=TestOrg and invitation=TestInvite' do + get 'auth/auth0?organization=TestOrg&invitation=TestInvite' + expect(last_response.status).to eq(302) + redirect_url = last_response.headers['Location'] + expect(redirect_url).to start_with('https://samples.auth0.com/authorize') + expect(redirect_url).to have_query('response_type', 'code') + expect(redirect_url).to have_query('state') + expect(redirect_url).to have_query('client_id') + expect(redirect_url).to have_query('redirect_uri') + expect(redirect_url).to have_query('organization', 'TestOrg') + expect(redirect_url).to have_query('invitation', 'TestInvite') + expect(redirect_url).not_to have_query('auth0Client') + expect(redirect_url).not_to have_query('connection') + expect(redirect_url).not_to have_query('connection_scope') + expect(redirect_url).not_to have_query('prompt') + expect(redirect_url).not_to have_query('screen_hint') + expect(redirect_url).not_to have_query('login_hint') + end + + it 'redirects to hosted login page with login_hint=example@mail.com' do + get 'auth/auth0?login_hint=example@mail.com' + expect(last_response.status).to eq(302) + redirect_url = last_response.headers['Location'] + expect(redirect_url).to start_with('https://samples.auth0.com/authorize') + expect(redirect_url).to have_query('response_type', 'code') + expect(redirect_url).to have_query('state') + expect(redirect_url).to have_query('client_id') + expect(redirect_url).to have_query('redirect_uri') + expect(redirect_url).to have_query('login_hint', 'example@mail.com') + expect(redirect_url).not_to have_query('auth0Client') + expect(redirect_url).not_to have_query('connection') + expect(redirect_url).not_to have_query('connection_scope') + expect(redirect_url).not_to have_query('prompt') + expect(redirect_url).not_to have_query('screen_hint') + expect(redirect_url).not_to have_query('organization') + expect(redirect_url).not_to have_query('invitation') + end + + it "stores session['authorize_params'] as a plain Ruby Hash" do + get '/auth/auth0' + expect(session['authorize_params'].class).to eq(::Hash) + end +end + +RSpec.shared_examples 'basic oauth callback assertions' do + it 'to succeed' do + expect(last_response.status).to eq(200) + end + + it 'has credentials' do + expect(subject['credentials']['token']).to eq(access_token) + expect(subject['credentials']['expires']).to be true + expect(subject['credentials']['expires_at']).to_not be_nil + end + + it 'has basic values' do + expect(subject['provider']).to eq('auth0') + expect(subject['uid']).to eq(user_id) + expect(subject['info']['name']).to eq(name) + end + + it 'should use the user info endpoint' do + expect(subject['extra']['raw_info']).to eq(basic_user_info) + end +end + +RSpec.shared_examples 'basic oauth refresh token callback assertions' do + it 'to succeed' do + expect(last_response.status).to eq(200) + end + + it 'has credentials' do + expect(subject['credentials']['token']).to eq(access_token) + expect(subject['credentials']['refresh_token']).to eq(refresh_token) + expect(subject['credentials']['expires']).to be true + expect(subject['credentials']['expires_at']).to_not be_nil + end +end + +RSpec.shared_examples 'oidc callback assertions' do + it 'to succeed' do + expect(last_response.status).to eq(200) + end + + it 'has credentials' do + expect(subject['credentials']['token']).to eq(access_token) + expect(subject['credentials']['expires']).to be true + expect(subject['credentials']['expires_at']).to_not be_nil + expect(subject['credentials']['id_token']).to eq(id_token) + end + + it 'has basic values' do + expect(subject['provider']).to eq('auth0') + expect(subject['uid']).to eq(user_id) + end + + it 'has info' do + expect(subject['info']['name']).to eq(name) + expect(subject['info']['nickname']).to eq(nickname) + expect(subject['info']['image']).to eq(picture) + expect(subject['info']['email']).to eq(email) + end + + it 'has extra' do + expect(subject['extra']['raw_info']['email_verified']).to be true + end +end + describe OmniAuth::Strategies::Auth0 do let(:client_id) { 'CLIENT_ID' } let(:client_secret) { 'CLIENT_SECRET' } let(:domain_url) { 'https://samples.auth0.com' } + let(:client_assertion_signing_algorithm) { 'RS256' } + let(:client_assertion_signing_key) { OpenSSL::PKey::RSA.generate(2048) } let(:application) do lambda do [200, {}, ['Hello.']] @@ -27,176 +248,97 @@ domain_url ) end - - describe 'client_options' do - let(:subject) { OmniAuth::Strategies::Auth0.new( + let(:auth0_client_assertion_signing_key) do + OmniAuth::Strategies::Auth0.new( application, client_id, - client_secret, - domain_url - ).client } - - context 'domain with https' do - let(:domain_url) { 'https://samples.auth0.com' } - it_behaves_like 'site has valid domain url', 'https://samples.auth0.com' - end - - context 'domain with http' do - let(:domain_url) { 'http://mydomain.com' } - it_behaves_like 'site has valid domain url', 'http://mydomain.com' - end - - context 'domain with host only' do - let(:domain_url) { 'samples.auth0.com' } - it_behaves_like 'site has valid domain url', 'https://samples.auth0.com' + nil, + domain_url, + { client_assertion_signing_key: client_assertion_signing_key, + client_assertion_signing_algorithm: client_assertion_signing_algorithm} + ) + end + describe 'client_options' do + context 'when using client_secret authentication' do + let(:subject) { OmniAuth::Strategies::Auth0.new( + application, + client_id, + client_secret, + domain_url + ).client } + + it_behaves_like 'client_options with valid configuration' end - it 'should have correct authorize path' do - expect(subject.options[:authorize_url]).to eq('/authorize') - end + context 'when using client assertion signing key authentication' do + let(:subject) do + OmniAuth::Strategies::Auth0.new( + application, + client_id, + nil, + domain_url, + { client_assertion_signing_key: client_assertion_signing_key, + client_assertion_signing_algorithm: client_assertion_signing_algorithm } + ).client + end - it 'should have the correct userinfo path' do - expect(subject.options[:userinfo_url]).to eq('/userinfo') - end + it_behaves_like 'client_options with valid configuration' - it 'should have the correct token path' do - expect(subject.options[:token_url]).to eq('/oauth/token') + it 'should have the correct auth_scheme' do + expect(subject.options[:auth_scheme]).to eq(:request_body) + end end end describe 'options' do - let(:subject) { auth0.options } + context 'when using client_secret authentication' do + let(:subject) { auth0.options } - it 'should have the correct client_id' do - expect(subject[:client_id]).to eq(client_id) - end + it 'should have the correct client_id' do + expect(subject[:client_id]).to eq(client_id) + end - it 'should have the correct client secret' do - expect(subject[:client_secret]).to eq(client_secret) - end - it 'should have correct domain' do - expect(subject[:domain]).to eq(domain_url) + it 'should have the correct client secret' do + expect(subject[:client_secret]).to eq(client_secret) + end + it 'should have correct domain' do + expect(subject[:domain]).to eq(domain_url) + end end - end - describe 'oauth' do - it 'redirects to hosted login page' do - get 'auth/auth0' - expect(last_response.status).to eq(302) - redirect_url = last_response.headers['Location'] - expect(redirect_url).to start_with('https://samples.auth0.com/authorize') - expect(redirect_url).to have_query('response_type', 'code') - expect(redirect_url).to have_query('state') - expect(redirect_url).to have_query('client_id') - expect(redirect_url).to have_query('redirect_uri') - expect(redirect_url).not_to have_query('auth0Client') - expect(redirect_url).not_to have_query('connection') - expect(redirect_url).not_to have_query('connection_scope') - expect(redirect_url).not_to have_query('prompt') - expect(redirect_url).not_to have_query('screen_hint') - expect(redirect_url).not_to have_query('login_hint') - expect(redirect_url).not_to have_query('organization') - expect(redirect_url).not_to have_query('invitation') - end + context 'when using client assertion signing key authentication' do + let(:subject) { auth0_client_assertion_signing_key.options } - it 'redirects to hosted login page' do - get 'auth/auth0?connection=abcd' - expect(last_response.status).to eq(302) - redirect_url = last_response.headers['Location'] - expect(redirect_url).to start_with('https://samples.auth0.com/authorize') - expect(redirect_url).to have_query('response_type', 'code') - expect(redirect_url).to have_query('state') - expect(redirect_url).to have_query('client_id') - expect(redirect_url).to have_query('redirect_uri') - expect(redirect_url).to have_query('connection', 'abcd') - expect(redirect_url).not_to have_query('auth0Client') - expect(redirect_url).not_to have_query('connection_scope') - expect(redirect_url).not_to have_query('prompt') - expect(redirect_url).not_to have_query('screen_hint') - expect(redirect_url).not_to have_query('login_hint') - expect(redirect_url).not_to have_query('organization') - expect(redirect_url).not_to have_query('invitation') - end + it 'should have the correct client_id' do + expect(subject[:client_id]).to eq(client_id) + end - it 'redirects to the hosted login page with connection_scope' do - get 'auth/auth0?connection_scope=identity_provider_scope' - expect(last_response.status).to eq(302) - redirect_url = last_response.headers['Location'] - expect(redirect_url).to start_with('https://samples.auth0.com/authorize') - expect(redirect_url) - .to have_query('connection_scope', 'identity_provider_scope') - end + it 'should have the correct client secret' do + expect(subject[:client_secret]).to eq(nil) + end + it 'should have correct domain' do + expect(subject[:domain]).to eq(domain_url) + end - it 'redirects to hosted login page with prompt=login' do - get 'auth/auth0?prompt=login' - expect(last_response.status).to eq(302) - redirect_url = last_response.headers['Location'] - expect(redirect_url).to start_with('https://samples.auth0.com/authorize') - expect(redirect_url).to have_query('response_type', 'code') - expect(redirect_url).to have_query('state') - expect(redirect_url).to have_query('client_id') - expect(redirect_url).to have_query('redirect_uri') - expect(redirect_url).to have_query('prompt', 'login') - expect(redirect_url).not_to have_query('auth0Client') - expect(redirect_url).not_to have_query('connection') - expect(redirect_url).not_to have_query('login_hint') - expect(redirect_url).not_to have_query('organization') - expect(redirect_url).not_to have_query('invitation') + it 'should have the correct client_assertion_signing_key' do + expect(subject[:client_assertion_signing_key]).to eq(client_assertion_signing_key) + end end + end - it 'redirects to hosted login page with screen_hint=signup' do - get 'auth/auth0?screen_hint=signup' - expect(last_response.status).to eq(302) - redirect_url = last_response.headers['Location'] - expect(redirect_url).to start_with('https://samples.auth0.com/authorize') - expect(redirect_url).to have_query('response_type', 'code') - expect(redirect_url).to have_query('state') - expect(redirect_url).to have_query('client_id') - expect(redirect_url).to have_query('redirect_uri') - expect(redirect_url).to have_query('screen_hint', 'signup') - expect(redirect_url).not_to have_query('auth0Client') - expect(redirect_url).not_to have_query('connection') - expect(redirect_url).not_to have_query('login_hint') - expect(redirect_url).not_to have_query('organization') - expect(redirect_url).not_to have_query('invitation') + describe 'oauth' do + context 'when using client_secret authentication' do + it_behaves_like 'oauth redirects with various parameters' end - it 'redirects to hosted login page with organization=TestOrg and invitation=TestInvite' do - get 'auth/auth0?organization=TestOrg&invitation=TestInvite' - expect(last_response.status).to eq(302) - redirect_url = last_response.headers['Location'] - expect(redirect_url).to start_with('https://samples.auth0.com/authorize') - expect(redirect_url).to have_query('response_type', 'code') - expect(redirect_url).to have_query('state') - expect(redirect_url).to have_query('client_id') - expect(redirect_url).to have_query('redirect_uri') - expect(redirect_url).to have_query('organization', 'TestOrg') - expect(redirect_url).to have_query('invitation', 'TestInvite') - expect(redirect_url).not_to have_query('auth0Client') - expect(redirect_url).not_to have_query('connection') - expect(redirect_url).not_to have_query('connection_scope') - expect(redirect_url).not_to have_query('prompt') - expect(redirect_url).not_to have_query('screen_hint') - expect(redirect_url).not_to have_query('login_hint') - end + context 'when using client assertion signing key authentication' do + before do + @app = make_application(client_secret: nil, + client_assertion_signing_key: client_assertion_signing_key, + client_assertion_signing_algorithm: client_assertion_signing_algorithm) + end - it 'redirects to hosted login page with login_hint=example@mail.com' do - get 'auth/auth0?login_hint=example@mail.com' - expect(last_response.status).to eq(302) - redirect_url = last_response.headers['Location'] - expect(redirect_url).to start_with('https://samples.auth0.com/authorize') - expect(redirect_url).to have_query('response_type', 'code') - expect(redirect_url).to have_query('state') - expect(redirect_url).to have_query('client_id') - expect(redirect_url).to have_query('redirect_uri') - expect(redirect_url).to have_query('login_hint', 'example@mail.com') - expect(redirect_url).not_to have_query('auth0Client') - expect(redirect_url).not_to have_query('connection') - expect(redirect_url).not_to have_query('connection_scope') - expect(redirect_url).not_to have_query('prompt') - expect(redirect_url).not_to have_query('screen_hint') - expect(redirect_url).not_to have_query('organization') - expect(redirect_url).not_to have_query('invitation') + it_behaves_like 'oauth redirects with various parameters' end def session @@ -206,12 +348,6 @@ def session Marshal.load(decoded_session_data) end - it "stores session['authorize_params'] as a plain Ruby Hash" do - get '/auth/auth0' - - expect(session['authorize_params'].class).to eq(::Hash) - end - describe 'callback' do let(:access_token) { 'access token' } let(:expires_in) { 2000 } @@ -227,20 +363,6 @@ def session let(:email) { 'mail@mail.com' } let(:email_verified) { true } - let(:id_token) do - payload = {} - payload['sub'] = user_id - payload['iss'] = "#{domain_url}/" - payload['aud'] = client_id - payload['name'] = name - payload['nickname'] = nickname - payload['picture'] = picture - payload['email'] = email - payload['email_verified'] = email_verified - - JWT.encode payload, client_secret, 'HS256' - end - let(:oauth_response) do { access_token: access_token, @@ -260,15 +382,6 @@ def session let(:basic_user_info) { { "sub" => user_id, "name" => name } } - def stub_auth(body) - stub_request(:post, 'https://samples.auth0.com/oauth/token') - .with(headers: { 'Auth0-Client' => telemetry_value }) - .to_return( - headers: { 'Content-Type' => 'application/json' }, - body: MultiJson.encode(body) - ) - end - def stub_userinfo(body) stub_request(:get, 'https://samples.auth0.com/userinfo') .to_return( @@ -290,91 +403,217 @@ def trigger_callback MultiJson.decode(last_response.body) end - context 'basic oauth' do - before do - stub_auth(oauth_response) - stub_userinfo(basic_user_info) - trigger_callback + context 'when using client_secret authentication' do + let(:id_token) do + payload = {} + payload['sub'] = user_id + payload['iss'] = "#{domain_url}/" + payload['aud'] = client_id + payload['name'] = name + payload['nickname'] = nickname + payload['picture'] = picture + payload['email'] = email + payload['email_verified'] = email_verified + + JWT.encode payload, client_secret, 'HS256' end - it 'to succeed' do - expect(last_response.status).to eq(200) + def stub_auth(body) + stub_request(:post, 'https://samples.auth0.com/oauth/token') + .with(headers: { 'Auth0-Client' => telemetry_value }) + .to_return( + headers: { 'Content-Type' => 'application/json' }, + body: MultiJson.encode(body) + ) end - it 'has credentials' do - expect(subject['credentials']['token']).to eq(access_token) - expect(subject['credentials']['expires']).to be true - expect(subject['credentials']['expires_at']).to_not be_nil + context 'basic oauth' do + before do + stub_auth(oauth_response) + stub_userinfo(basic_user_info) + trigger_callback + end + + it_behaves_like 'basic oauth callback assertions' end - it 'has basic values' do - expect(subject['provider']).to eq('auth0') - expect(subject['uid']).to eq(user_id) - expect(subject['info']['name']).to eq(name) + context 'basic oauth w/refresh token' do + before do + stub_auth(oauth_response.merge(refresh_token: refresh_token)) + stub_userinfo(basic_user_info) + trigger_callback + end + + it_behaves_like 'basic oauth refresh token callback assertions' end - it 'should use the user info endpoint' do - expect(subject['extra']['raw_info']).to eq(basic_user_info) + context 'oidc' do + before do + stub_auth(oidc_response) + trigger_callback + end + + it_behaves_like 'oidc callback assertions' end end - context 'basic oauth w/refresh token' do - before do - stub_auth(oauth_response.merge(refresh_token: refresh_token)) - stub_userinfo(basic_user_info) - trigger_callback + context 'when using client assertion signing key authentication' do + let(:jwt_token) { JWT.encode({ sub: client_id }, client_assertion_signing_key, 'RS256') } + let(:valid_jwks_kid) { 'NkJCQzIyQzRBMEU4NjhGNUU4MzU4RkY0M0ZDQzkwOUQ0Q0VGNUMwQg' } + + let(:rsa_private_key) do + OpenSSL::PKey::RSA.generate 2048 end - it 'to succeed' do - expect(last_response.status).to eq(200) + let(:valid_jwks) do + { + keys: [ + { + kid: valid_jwks_kid, + x5c: [Base64.encode64(make_cert(rsa_private_key).to_der)] + } + ] + }.to_json end - it 'has credentials' do - expect(subject['credentials']['token']).to eq(access_token) - expect(subject['credentials']['refresh_token']).to eq(refresh_token) - expect(subject['credentials']['expires']).to be true - expect(subject['credentials']['expires_at']).to_not be_nil + let(:id_token) do + payload = {} + payload['sub'] = user_id + payload['iss'] = "#{domain_url}/" + payload['aud'] = client_id + payload['name'] = name + payload['nickname'] = nickname + payload['picture'] = picture + payload['email'] = email + payload['email_verified'] = email_verified + + JWT.encode payload, rsa_private_key, 'RS256', kid: valid_jwks_kid end - end - context 'oidc' do - before do - stub_auth(oidc_response) - trigger_callback + def jwt_token?(token) + JWT.decode(token, nil, false) + true + rescue JWT::DecodeError, ArgumentError + false end - it 'to succeed' do - expect(last_response.status).to eq(200) + def make_cert(private_key) + cert = OpenSSL::X509::Certificate.new + cert.issuer = OpenSSL::X509::Name.parse('/C=BE/O=Auth0/OU=Auth0/CN=Auth0') + cert.subject = cert.issuer + cert.not_before = Time.now + cert.not_after = Time.now + 365 * 24 * 60 * 60 + cert.public_key = private_key.public_key + cert.serial = 0x0 + cert.version = 2 + + ef = OpenSSL::X509::ExtensionFactory.new + ef.subject_certificate = cert + ef.issuer_certificate = cert + cert.extensions = [ + ef.create_extension('basicConstraints', 'CA:TRUE', true), + ef.create_extension('subjectKeyIdentifier', 'hash') + ] + cert.add_extension ef.create_extension( + 'authorityKeyIdentifier', + 'keyid:always,issuer:always' + ) + + cert.sign private_key, OpenSSL::Digest.new('SHA1') end - it 'has credentials' do - expect(subject['credentials']['token']).to eq(access_token) - expect(subject['credentials']['expires']).to be true - expect(subject['credentials']['expires_at']).to_not be_nil - expect(subject['credentials']['id_token']).to eq(id_token) + def stub_auth(body, stubbed_jwt_token: true) + stub_request(:post, "#{domain_url}/oauth/token") + .with do |request| + params = URI.decode_www_form(request.body).to_h + token = params['client_assertion'] + + request.headers['Auth0-Client'] == telemetry_value && + params['grant_type'] == described_class::AUTHORIZATION_CODE_GRANT_TYPE && + params['client_id'] == client_id && + params['client_assertion_type'] == described_class::CLIENT_ASSERTION_TYPE && + (stubbed_jwt_token ? token == jwt_token : jwt_token?(token)) + end + .to_return( + headers: { 'Content-Type' => 'application/json' }, + body: MultiJson.encode(body) + ) end - it 'has basic values' do - expect(subject['provider']).to eq('auth0') - expect(subject['uid']).to eq(user_id) + def stub_expected_jwks + stub_request(:get, 'https://samples.auth0.com/.well-known/jwks.json') + .to_return( + headers: { 'Content-Type' => 'application/json' }, + body: valid_jwks, + status: 200 + ) end - it 'has info' do - expect(subject['info']['name']).to eq(name) - expect(subject['info']['nickname']).to eq(nickname) - expect(subject['info']['image']).to eq(picture) - expect(subject['info']['email']).to eq(email) + def stub_jwt_token(algorithm: client_assertion_signing_algorithm) + allow(OmniAuth::Auth0::JWTToken).to receive(:new) + .with(client_id, + domain_url, + client_assertion_signing_key, + algorithm) + .and_return(instance_double(OmniAuth::Auth0::JWTToken, jwt_token: jwt_token)) end - it 'has extra' do - expect(subject['extra']['raw_info']['email_verified']).to be true + context 'basic oauth' do + before do + @app = make_application(client_secret: nil, client_assertion_signing_key: client_assertion_signing_key) + stub_jwt_token(algorithm: nil) + stub_auth(oauth_response) + stub_userinfo(basic_user_info) + trigger_callback + end + + it_behaves_like 'basic oauth callback assertions' + end + + context 'basic oath without stubbing jwt token' do + before do + @app = make_application(client_secret: nil, client_assertion_signing_key: client_assertion_signing_key) + stub_auth(oauth_response, stubbed_jwt_token: false) + stub_userinfo(basic_user_info) + trigger_callback + end + + it_behaves_like 'basic oauth callback assertions' + end + + context 'basic oauth w/refresh token' do + before do + @app = make_application(client_secret: nil, + client_assertion_signing_key: client_assertion_signing_key, + client_assertion_signing_algorithm: client_assertion_signing_algorithm) + stub_jwt_token + stub_auth(oauth_response.merge(refresh_token: refresh_token)) + stub_userinfo(basic_user_info) + trigger_callback + end + + it_behaves_like 'basic oauth refresh token callback assertions' + end + + context 'oidc' do + before do + @app = make_application(client_secret: nil, + client_assertion_signing_key: client_assertion_signing_key, + client_assertion_signing_algorithm: client_assertion_signing_algorithm) + stub_jwt_token + stub_auth(oidc_response) + stub_expected_jwks + trigger_callback + end + + it_behaves_like 'oidc callback assertions' end end end end describe 'error_handling' do - it 'fails when missing client_id' do + it 'fails when missing client_id and client_assertion_signing_key' do @app = make_application(client_id: nil) get 'auth/auth0' expect(last_response.status).to eq(302) @@ -382,7 +621,7 @@ def trigger_callback expect(redirect_url).to fail_auth_with('missing_client_id') end - it 'fails when missing client_secret' do + it 'fails when missing client_secret and client_assertion_signing_key' do @app = make_application(client_secret: nil) get 'auth/auth0' expect(last_response.status).to eq(302) @@ -397,6 +636,14 @@ def trigger_callback redirect_url = last_response.headers['Location'] expect(redirect_url).to fail_auth_with('missing_domain') end + + it 'fails when missing client_assertion_signing_key' do + @app = make_application(client_secret: nil, client_assertion_signing_key: nil) + get 'auth/auth0' + expect(last_response.status).to eq(302) + redirect_url = last_response.headers['Location'] + expect(redirect_url).to fail_auth_with('missing_client_assertion_signing_key') + end end end From 8237cdfc3cf04b62a3daeff749720935ff712aba Mon Sep 17 00:00:00 2001 From: arpit-jain_atko Date: Tue, 12 May 2026 16:33:51 +0530 Subject: [PATCH 11/18] Release v3.2.0 --- .version | 2 +- CHANGELOG.md | 11 +++ Gemfile.lock | 173 +++++++++++++++++++--------------- lib/omniauth-auth0/version.rb | 2 +- 4 files changed, 111 insertions(+), 77 deletions(-) diff --git a/.version b/.version index 50e47c8..a4f52a5 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -3.1.1 \ No newline at end of file +3.2.0 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index fd800f0..c241fef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Change Log +## [v3.2.0](https://github.com/auth0/omniauth-auth0/tree/v3.2.0) (2026-05-12) +[Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v3.1.1...v3.2.0) + +**Added** +- Add support for client assertion signing key authentication [\#203](https://github.com/auth0/omniauth-auth0/pull/203) ([kaczowkad](https://github.com/kaczowkad)) + +**Security** +- Bump faraday from 2.7.10 to 2.14.1 [\#215](https://github.com/auth0/omniauth-auth0/pull/215) ([dependabot[bot]](https://github.com/apps/dependabot)) +- Bump rack from 2.2.7 to 2.2.23 [\#217](https://github.com/auth0/omniauth-auth0/pull/217) ([dependabot[bot]](https://github.com/apps/dependabot)) +- chore: Bump rexml from 3.2.5 to 3.3.9 [\#206](https://github.com/auth0/omniauth-auth0/pull/206) ([arpit-jn](https://github.com/arpit-jn)) + ## [v3.2.0](https://github.com/auth0/omniauth-auth0/tree/v3.2.0) (2023-07-14) [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v3.1.0...v3.2.0) diff --git a/Gemfile.lock b/Gemfile.lock index 08c0f2a..5e70b44 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - omniauth-auth0 (3.1.1) + omniauth-auth0 (3.2.0) jwt (~> 2) omniauth (~> 2) omniauth-oauth2 (~> 1) @@ -9,15 +9,18 @@ PATH GEM remote: https://rubygems.org/ specs: - addressable (2.8.4) - public_suffix (>= 2.0.2, < 6.0) - ast (2.4.2) + addressable (2.9.0) + public_suffix (>= 2.0.2, < 8.0) + ast (2.4.3) + base64 (0.3.0) + bigdecimal (4.1.2) coderay (1.1.3) - crack (0.4.5) + crack (1.0.1) + bigdecimal rexml daemons (1.4.1) - diff-lcs (1.5.0) - docile (1.4.0) + diff-lcs (1.6.2) + docile (1.4.1) dotenv (2.8.1) eventmachine (1.2.7) faraday (2.14.1) @@ -26,11 +29,16 @@ GEM logger faraday-net_http (3.4.2) net-http (~> 0.5) - ffi (1.15.5) - formatador (1.1.0) - guard (2.18.0) + ffi (1.17.4-aarch64-linux-gnu) + ffi (1.17.4-arm64-darwin) + ffi (1.17.4-x86_64-darwin) + ffi (1.17.4-x86_64-linux-gnu) + formatador (1.2.3) + reline + guard (2.20.1) formatador (>= 0.2.4) listen (>= 2.7, < 4.0) + logger (~> 1.6) lumberjack (>= 1.0.12, < 2.0) nenv (~> 0.1) notiffany (~> 0.0) @@ -42,90 +50,102 @@ GEM guard (~> 2.1) guard-compat (~> 1.1) rspec (>= 2.99.0, < 4.0) - hashdiff (1.0.1) - hashie (5.0.0) - json (2.6.3) - jwt (2.7.1) - language_server-protocol (3.17.0.3) - listen (3.8.0) + hashdiff (1.2.1) + hashie (5.1.0) + logger + io-console (0.8.2) + json (2.19.5) + jwt (2.10.2) + base64 + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + listen (3.10.0) + logger rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) logger (1.7.0) - lumberjack (1.2.8) - method_source (1.0.0) - multi_json (1.15.0) - multi_xml (0.6.0) - mustermann (3.0.0) - ruby2_keywords (~> 0.0.1) + lumberjack (1.4.2) + method_source (1.1.0) + multi_json (1.21.1) + multi_xml (0.9.1) + bigdecimal (>= 3.1, < 5) + mustermann (3.1.1) nenv (0.3.0) net-http (0.9.1) uri (>= 0.11.1) notiffany (0.1.3) nenv (~> 0.1) shellany (~> 0.0) - oauth2 (2.0.9) - faraday (>= 0.17.3, < 3.0) - jwt (>= 1.0, < 3.0) + oauth2 (2.0.18) + faraday (>= 0.17.3, < 4.0) + jwt (>= 1.0, < 4.0) + logger (~> 1.2) multi_xml (~> 0.5) rack (>= 1.2, < 4) - snaky_hash (~> 2.0) - version_gem (~> 1.1) - omniauth (2.1.1) + snaky_hash (~> 2.0, >= 2.0.3) + version_gem (~> 1.1, >= 1.1.9) + omniauth (2.1.4) hashie (>= 3.4.6) + logger rack (>= 2.2.3) rack-protection - omniauth-oauth2 (1.8.0) - oauth2 (>= 1.4, < 3) + omniauth-oauth2 (1.9.0) + oauth2 (>= 2.0.2, < 3) omniauth (~> 2.0) - parallel (1.23.0) - parser (3.2.2.3) + parallel (2.1.0) + parser (3.3.11.1) ast (~> 2.4.1) racc - pry (0.14.2) + prism (1.9.0) + pry (0.16.0) coderay (~> 1.1) method_source (~> 1.0) - public_suffix (5.0.3) - racc (1.7.1) + reline (>= 0.6.0) + public_suffix (7.0.5) + racc (1.8.1) rack (2.2.23) - rack-protection (3.0.6) - rack - rack-test (2.1.0) + rack-protection (3.2.0) + base64 (>= 0.1.0) + rack (~> 2.2, >= 2.2.4) + rack-test (2.2.0) rack (>= 1.3) rainbow (3.1.1) - rake (13.0.6) + rake (13.4.2) rb-fsevent (0.11.2) - rb-inotify (0.10.1) + rb-inotify (0.11.1) ffi (~> 1.0) - regexp_parser (2.8.1) - rexml (3.3.9) - rspec (3.12.0) - rspec-core (~> 3.12.0) - rspec-expectations (~> 3.12.0) - rspec-mocks (~> 3.12.0) - rspec-core (3.12.2) - rspec-support (~> 3.12.0) - rspec-expectations (3.12.3) + regexp_parser (2.12.0) + reline (0.6.3) + io-console (~> 0.5) + rexml (3.4.4) + rspec (3.13.2) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.6) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.12.0) - rspec-mocks (3.12.6) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.8) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.12.0) - rspec-support (3.12.1) - rubocop (1.54.2) + rspec-support (~> 3.13.0) + rspec-support (3.13.7) + rubocop (1.86.1) json (~> 2.3) - language_server-protocol (>= 3.17.0) - parallel (~> 1.10) - parser (>= 3.2.2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (>= 1.10) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) - rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.28.0, < 2.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.49.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.29.0) - parser (>= 3.2.1.0) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.49.1) + parser (>= 3.3.7.2) + prism (~> 1.7) ruby-progressbar (1.13.0) - ruby2_keywords (0.0.5) shellany (0.0.1) shotgun (0.9.2) rack (>= 1.0) @@ -136,26 +156,28 @@ GEM simplecov-cobertura (2.1.0) rexml simplecov (~> 0.19) - simplecov-html (0.12.3) + simplecov-html (0.13.2) simplecov_json_formatter (0.1.4) - sinatra (3.0.6) + sinatra (3.2.0) mustermann (~> 3.0) rack (~> 2.2, >= 2.2.4) - rack-protection (= 3.0.6) + rack-protection (= 3.2.0) tilt (~> 2.0) - snaky_hash (2.0.1) - hashie - version_gem (~> 1.1, >= 1.1.1) + snaky_hash (2.0.3) + hashie (>= 0.1.0, < 6) + version_gem (>= 1.1.8, < 3) thin (1.8.2) daemons (~> 1.0, >= 1.0.9) eventmachine (~> 1.0, >= 1.0.4) rack (>= 1, < 3) - thor (1.2.2) - tilt (2.2.0) - unicode-display_width (2.4.2) + thor (1.5.0) + tilt (2.7.0) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.2.0) uri (1.1.1) - version_gem (1.1.3) - webmock (3.18.1) + version_gem (1.1.9) + webmock (3.26.2) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) @@ -165,6 +187,7 @@ PLATFORMS arm64-darwin-21 arm64-darwin-22 arm64-darwin-23 + arm64-darwin-25 x86_64-darwin-22 x86_64-linux diff --git a/lib/omniauth-auth0/version.rb b/lib/omniauth-auth0/version.rb index afc7afb..50e694c 100644 --- a/lib/omniauth-auth0/version.rb +++ b/lib/omniauth-auth0/version.rb @@ -1,5 +1,5 @@ module OmniAuth module Auth0 - VERSION = '3.1.1'.freeze + VERSION = '3.2.0'.freeze end end From f484dbc4c42d967e1edb27d1df1b0f731cd47919 Mon Sep 17 00:00:00 2001 From: arpit-jain_atko Date: Thu, 28 May 2026 11:05:24 +0530 Subject: [PATCH 12/18] ci: Use bundle update to avoid RubyGems CDN inconsistency with lockfile-pinned versions --- .github/actions/setup/action.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 5c45715..8209684 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -10,10 +10,6 @@ inputs: description: The path to the bundle cache required: false default: vendor/bundle - bundler-cache: - description: Whether to use the bundler cache - required: false - default: true runs: using: composite @@ -23,10 +19,10 @@ runs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ inputs.ruby }} - bundler-cache: ${{ inputs.bundle-cache }} + bundler-cache: false - name: Install dependencies - run: bundle check || bundle install + run: bundle update || bundle install shell: bash env: BUNDLE_PATH: ${{ inputs.bundle-path }} From afb8214d7852d8ac05849373d9a90741ae0afadf Mon Sep 17 00:00:00 2001 From: arpit-jain_atko Date: Thu, 28 May 2026 11:11:07 +0530 Subject: [PATCH 13/18] fix: Add missing require cgi for Ruby 3.0 compatibility in tests --- spec/omniauth/strategies/auth0_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/omniauth/strategies/auth0_spec.rb b/spec/omniauth/strategies/auth0_spec.rb index 4d47adf..60482fe 100644 --- a/spec/omniauth/strategies/auth0_spec.rb +++ b/spec/omniauth/strategies/auth0_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' require 'jwt' require 'multi_json' +require 'cgi' OmniAuth.config.allowed_request_methods = [:get, :post] From aeac2d5f2437d5ab266473629218d9e7a5d0c799 Mon Sep 17 00:00:00 2001 From: arpit-jain_atko Date: Thu, 28 May 2026 12:41:11 +0530 Subject: [PATCH 14/18] fix: Bump simplecov-cobertura to ~> 3.0 to fix rexml 3.4.4 compatibility crash in CI --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 78f32a4..92041bb 100644 --- a/Gemfile +++ b/Gemfile @@ -19,7 +19,7 @@ group :test do gem 'listen', '~> 3' gem 'rack-test', '~> 2', '>= 2.0.2' gem 'rspec', '~> 3' - gem 'simplecov-cobertura', '~> 2' + gem 'simplecov-cobertura', '~> 3.0' gem 'webmock', '~> 3' gem 'multi_json', '~> 1' end diff --git a/Gemfile.lock b/Gemfile.lock index 5e70b44..47100a4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -153,7 +153,7 @@ GEM docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) - simplecov-cobertura (2.1.0) + simplecov-cobertura (3.1.0) rexml simplecov (~> 0.19) simplecov-html (0.13.2) @@ -205,7 +205,7 @@ DEPENDENCIES rspec (~> 3) rubocop (~> 1) shotgun (~> 0, >= 0.9.2) - simplecov-cobertura (~> 2) + simplecov-cobertura (~> 3.0) sinatra (~> 3) thin (~> 1) webmock (~> 3) From 25279e3350dd1ab69a60076067727cb60fde95d4 Mon Sep 17 00:00:00 2001 From: arpit-jain_atko Date: Thu, 28 May 2026 12:53:03 +0530 Subject: [PATCH 15/18] docs: Remove phantom v3.2.0 changelog entry that was never published and consolidate into v3.1.1 --- CHANGELOG.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c241fef..43f5222 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,8 @@ - Bump rack from 2.2.7 to 2.2.23 [\#217](https://github.com/auth0/omniauth-auth0/pull/217) ([dependabot[bot]](https://github.com/apps/dependabot)) - chore: Bump rexml from 3.2.5 to 3.3.9 [\#206](https://github.com/auth0/omniauth-auth0/pull/206) ([arpit-jn](https://github.com/arpit-jn)) -## [v3.2.0](https://github.com/auth0/omniauth-auth0/tree/v3.2.0) (2023-07-14) -[Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v3.1.0...v3.2.0) +## [v3.1.1](https://github.com/auth0/omniauth-auth0/tree/v3.1.1) (2023-03-01) +[Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v3.1.0...v3.1.1) **Added** - [SDK-4410] Support Organization Name in JWT validation [\#184](https://github.com/auth0/omniauth-auth0/pull/184) ([stevehobbsdev](https://github.com/stevehobbsdev)) @@ -20,12 +20,6 @@ **Fixed** - fix: upgrade to Sinatra 3 and use Rack::Session::Cookie in tests [\#165](https://github.com/auth0/omniauth-auth0/pull/165) ([stevehobbsdev](https://github.com/stevehobbsdev)) -## [v3.1.1](https://github.com/auth0/omniauth-auth0/tree/v3.1.1) (2023-03-01) -[Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v3.1.0...v3.1.1) - -**Fixed** -- fix: upgrade to Sinatra 3 and use Rack::Session::Cookie in tests [\#165](https://github.com/auth0/omniauth-auth0/pull/165) ([stevehobbsdev](https://github.com/stevehobbsdev)) - ## [v3.1.0](https://github.com/auth0/omniauth-auth0/tree/v3.1.0) (2022-11-04) [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v3.0.0...v3.1.0) From 4eb8a378a16835c9efd523e4e90c9dfb0791cb1f Mon Sep 17 00:00:00 2001 From: arpit-jain_atko Date: Thu, 28 May 2026 13:01:12 +0530 Subject: [PATCH 16/18] docs: Update release date to today and rename Security section to Dependency Bumps --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43f5222..49a2464 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,15 @@ # Change Log -## [v3.2.0](https://github.com/auth0/omniauth-auth0/tree/v3.2.0) (2026-05-12) +## [v3.2.0](https://github.com/auth0/omniauth-auth0/tree/v3.2.0) (2026-05-27) [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v3.1.1...v3.2.0) **Added** - Add support for client assertion signing key authentication [\#203](https://github.com/auth0/omniauth-auth0/pull/203) ([kaczowkad](https://github.com/kaczowkad)) -**Security** +**Dependency Bumps** - Bump faraday from 2.7.10 to 2.14.1 [\#215](https://github.com/auth0/omniauth-auth0/pull/215) ([dependabot[bot]](https://github.com/apps/dependabot)) - Bump rack from 2.2.7 to 2.2.23 [\#217](https://github.com/auth0/omniauth-auth0/pull/217) ([dependabot[bot]](https://github.com/apps/dependabot)) -- chore: Bump rexml from 3.2.5 to 3.3.9 [\#206](https://github.com/auth0/omniauth-auth0/pull/206) ([arpit-jn](https://github.com/arpit-jn)) +- Bump rexml from 3.2.5 to 3.3.9 [\#206](https://github.com/auth0/omniauth-auth0/pull/206) ([arpit-jn](https://github.com/arpit-jn)) ## [v3.1.1](https://github.com/auth0/omniauth-auth0/tree/v3.1.1) (2023-03-01) [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v3.1.0...v3.1.1) From db0f71782de4a90037909d47047c50569dd4968c Mon Sep 17 00:00:00 2001 From: arpit-jain_atko Date: Thu, 28 May 2026 18:16:48 +0530 Subject: [PATCH 17/18] fix: Align release workflow with ruby-auth0 to fix broken publish pipeline --- .github/workflows/publish.yml | 15 ++++++--------- .github/workflows/rl-scanner.yml | 5 +---- .github/workflows/ruby-release.yml | 3 +-- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 00b6fbe..0433c61 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,16 +1,13 @@ -name: Publish Release +name: Create Release on: + pull_request: + types: + - closed workflow_dispatch: - inputs: - branch: - description: The branch to release from. - required: true - default: master - permissions: - contents: read + contents: write id-token: write # This is required for requesting the JWT jobs: @@ -26,7 +23,7 @@ jobs: PRODSEC_TOOLS_TOKEN: ${{ secrets.PRODSEC_TOOLS_TOKEN }} PRODSEC_TOOLS_ARN: ${{ secrets.PRODSEC_TOOLS_ARN }} - publish: + release: uses: ./.github/workflows/ruby-release.yml needs: rl-scanner with: diff --git a/.github/workflows/rl-scanner.yml b/.github/workflows/rl-scanner.yml index 627ae08..714d933 100644 --- a/.github/workflows/rl-scanner.yml +++ b/.github/workflows/rl-scanner.yml @@ -29,10 +29,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.event.inputs.branch }} + uses: actions/checkout@v6 - name: Configure Ruby uses: ./.github/actions/setup diff --git a/.github/workflows/ruby-release.yml b/.github/workflows/ruby-release.yml index 702a910..70438e9 100644 --- a/.github/workflows/ruby-release.yml +++ b/.github/workflows/ruby-release.yml @@ -20,10 +20,9 @@ jobs: steps: # Checkout the code - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 - ref: ${{ github.event.inputs.branch }} # Get the version from the branch name - id: get_version From 65a7f8405e6419de8581b41f6c1fc156491b2851 Mon Sep 17 00:00:00 2001 From: arpit-jain_atko Date: Thu, 28 May 2026 19:40:54 +0530 Subject: [PATCH 18/18] fix: Add v prefix to .version to match release branch naming convention --- .version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.version b/.version index a4f52a5..aa6c896 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -3.2.0 \ No newline at end of file +v3.2.0 \ No newline at end of file