├── .codecov.yml ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── Bug Report.yml │ ├── Feature Request.yml │ └── config.yml ├── PULL_REQUEST_TEMPLATE.md ├── actions │ ├── get-prerelease │ │ └── action.yml │ ├── get-release-notes │ │ └── action.yml │ ├── get-version │ │ └── action.yml │ ├── maven-publish │ │ └── action.yml │ ├── release-create │ │ └── action.yml │ ├── rl-scanner │ │ └── action.yml │ └── tag-exists │ │ └── action.yml ├── stale.yml └── workflows │ ├── build-and-test.yml │ ├── codeql.yml │ ├── dependabot.yml │ ├── java-release.yml │ ├── release.yml │ ├── rl-secure.yml │ └── semgrep.yml ├── .gitignore ├── .idea └── vcs.xml ├── .shiprc ├── .version ├── CHANGELOG.md ├── EXAMPLES.md ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle ├── maven-publish.gradle ├── versioning.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── opslevel.yml ├── settings.gradle └── src ├── main └── java │ └── com │ └── auth0 │ ├── AlgorithmNameVerifier.java │ ├── AsymmetricSignatureVerifier.java │ ├── AuthCookie.java │ ├── AuthenticationController.java │ ├── AuthorizeUrl.java │ ├── IdTokenVerifier.java │ ├── IdentityVerificationException.java │ ├── InvalidRequestException.java │ ├── RandomStorage.java │ ├── RequestProcessor.java │ ├── SameSite.java │ ├── SessionUtils.java │ ├── SignatureVerifier.java │ ├── StorageUtils.java │ ├── SymmetricSignatureVerifier.java │ ├── TokenValidationException.java │ ├── Tokens.java │ └── TransientCookieStore.java └── test ├── java └── com │ └── auth0 │ ├── AuthenticationControllerTest.java │ ├── AuthorizeUrlTest.java │ ├── IdTokenVerifierTest.java │ ├── IdentityVerificationExceptionMatcher.java │ ├── IdentityVerificationExceptionTest.java │ ├── InvalidRequestExceptionMatcher.java │ ├── InvalidRequestExceptionTest.java │ ├── RandomStorageTest.java │ ├── RequestProcessorTest.java │ ├── SessionUtilsTest.java │ ├── SignatureVerifierTest.java │ ├── TokensTest.java │ └── TransientCookieStoreTest.java └── resources ├── bad-public.pem ├── certificate.pem ├── private.pem └── public.pem /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | precision: 2 3 | round: down 4 | range: "70...100" 5 | status: 6 | patch: 7 | default: 8 | threshold: 2% 9 | if_no_uploads: error 10 | changes: true 11 | project: 12 | default: 13 | target: auto 14 | threshold: 2% 15 | if_no_uploads: error 16 | comment: false 17 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @auth0/project-dx-sdks-engineer-codeowner 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug Report.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Report a bug 2 | description: Have you found a bug or issue? Create a bug report for this library 3 | labels: ["bug"] 4 | 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | **Please do not report security vulnerabilities here**. The [Responsible Disclosure Program](https://auth0.com/responsible-disclosure-policy) details the procedure for disclosing security issues. 10 | 11 | - type: checkboxes 12 | id: checklist 13 | attributes: 14 | label: Checklist 15 | options: 16 | - label: The issue can be reproduced in the [mvc-auth-commons sample app](https://github.com/auth0-samples/auth0-servlet-sample/tree/master/01-Login) (or N/A). 17 | required: true 18 | - label: I have looked into the [Readme](https://github.com/auth0/auth0-java-mvc-common#readme) and the [Examples](https://github.com/auth0/auth0-java-mvc-common/blob/master/EXAMPLES.md), and have not found a suitable solution or answer. 19 | required: true 20 | - label: I have looked into the [API documentation](https://javadoc.io/doc/com.auth0/mvc-auth-commons/latest/index.html) and have not found a suitable solution or answer. 21 | required: true 22 | - label: I have searched the [issues](https://github.com/auth0/auth0-java-mvc-common/issues) and have not found a suitable solution or answer. 23 | required: true 24 | - label: I have searched the [Auth0 Community](https://community.auth0.com) forums and have not found a suitable solution or answer. 25 | required: true 26 | - label: I agree to the terms within the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md). 27 | required: true 28 | 29 | - type: textarea 30 | id: description 31 | attributes: 32 | label: Description 33 | description: Provide a clear and concise description of the issue, including what you expected to happen. 34 | validations: 35 | required: true 36 | 37 | - type: textarea 38 | id: reproduction 39 | attributes: 40 | label: Reproduction 41 | description: Detail the steps taken to reproduce this error, and whether this issue can be reproduced consistently or if it is intermittent. 42 | placeholder: | 43 | 1. Step 1... 44 | 2. Step 2... 45 | 3. ... 46 | validations: 47 | required: true 48 | 49 | - type: textarea 50 | id: additional-context 51 | attributes: 52 | label: Additional context 53 | description: Other libraries that might be involved, or any other relevant information you think would be useful. 54 | validations: 55 | required: false 56 | 57 | - type: input 58 | id: environment-version 59 | attributes: 60 | label: mvc-auth-commons version 61 | validations: 62 | required: true 63 | 64 | - type: input 65 | id: environment-java-version 66 | attributes: 67 | label: Java version 68 | validations: 69 | required: true 70 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature Request.yml: -------------------------------------------------------------------------------- 1 | name: 🧩 Feature request 2 | description: Suggest an idea or a feature for this library 3 | labels: ["feature request"] 4 | 5 | body: 6 | - type: checkboxes 7 | id: checklist 8 | attributes: 9 | label: Checklist 10 | options: 11 | - label: I have looked into the [Readme](https://github.com/auth0/auth0-java-mvc-common#readme) and the [Examples](https://github.com/auth0/auth0-java-mvc-common/blob/master/EXAMPLES.md), and have not found a suitable solution or answer. 12 | required: true 13 | - label: I have looked into the [API documentation](https://javadoc.io/doc/com.auth0/mvc-auth-commons/latest/index.html) and have not found a suitable solution or answer. 14 | required: true 15 | - label: I have searched the [issues](https://github.com/auth0/auth0-java-mvc-common/issues) and have not found a suitable solution or answer. 16 | required: true 17 | - label: I have searched the [Auth0 Community](https://community.auth0.com) forums and have not found a suitable solution or answer. 18 | required: true 19 | - label: I agree to the terms within the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md). 20 | required: true 21 | 22 | - type: textarea 23 | id: description 24 | attributes: 25 | label: Describe the problem you'd like to have solved 26 | description: A clear and concise description of what the problem is. 27 | placeholder: I'm always frustrated when... 28 | validations: 29 | required: true 30 | 31 | - type: textarea 32 | id: ideal-solution 33 | attributes: 34 | label: Describe the ideal solution 35 | description: A clear and concise description of what you want to happen. 36 | validations: 37 | required: true 38 | 39 | - type: textarea 40 | id: alternatives-and-workarounds 41 | attributes: 42 | label: Alternatives and current workarounds 43 | description: A clear and concise description of any alternatives you've considered or any workarounds that are currently in place. 44 | validations: 45 | required: false 46 | 47 | - type: textarea 48 | id: additional-context 49 | attributes: 50 | label: Additional context 51 | description: Add any other context or screenshots about the feature request here. 52 | validations: 53 | required: false 54 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Auth0 Community 4 | url: https://community.auth0.com 5 | about: Discuss this SDK in the Auth0 Community forums 6 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Changes 2 | 3 | Please describe both what is changing and why this is important. Include: 4 | 5 | - Endpoints added, deleted, deprecated, or changed 6 | - Classes and methods added, deleted, deprecated, or changed 7 | - Screenshots of new or changed UI, if applicable 8 | - A summary of usage if this is a new feature or change to a public API (this should also be added to relevant documentation once released) 9 | - Any alternative designs or approaches considered 10 | 11 | ### References 12 | 13 | Please include relevant links supporting this change such as a: 14 | 15 | - support ticket 16 | - community post 17 | - StackOverflow post 18 | - support forum thread 19 | 20 | ### Testing 21 | 22 | Please describe how this can be tested by reviewers. Be specific about anything not tested and reasons why. If this library has unit and/or integration testing, tests should be added for new functionality and existing tests should complete without errors. 23 | 24 | - [ ] This change adds test coverage 25 | - [ ] This change has been tested on the latest version of Java or why not 26 | 27 | ### Checklist 28 | 29 | - [ ] I have read the [Auth0 general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md) 30 | - [ ] I have read the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md) 31 | - [ ] All existing and new tests complete without errors 32 | -------------------------------------------------------------------------------- /.github/actions/get-prerelease/action.yml: -------------------------------------------------------------------------------- 1 | name: Return a boolean indicating if the version contains prerelease identifiers 2 | 3 | # 4 | # Returns a simple true/false boolean indicating whether the version indicates it's a prerelease or not. 5 | # 6 | # TODO: Remove once the common repo is public. 7 | # 8 | 9 | inputs: 10 | version: 11 | required: true 12 | 13 | outputs: 14 | prerelease: 15 | value: ${{ steps.get_prerelease.outputs.PRERELEASE }} 16 | 17 | runs: 18 | using: composite 19 | 20 | steps: 21 | - id: get_prerelease 22 | shell: bash 23 | run: | 24 | if [[ "${VERSION}" == *"beta"* || "${VERSION}" == *"alpha"* ]]; then 25 | echo "PRERELEASE=true" >> $GITHUB_OUTPUT 26 | else 27 | echo "PRERELEASE=false" >> $GITHUB_OUTPUT 28 | fi 29 | env: 30 | VERSION: ${{ inputs.version }} 31 | -------------------------------------------------------------------------------- /.github/actions/get-release-notes/action.yml: -------------------------------------------------------------------------------- 1 | name: Return the release notes extracted from the body of the PR associated with the release. 2 | 3 | # 4 | # 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. 5 | # 6 | # TODO: Remove once the common repo is public. 7 | # 8 | inputs: 9 | version: 10 | required: true 11 | repo_name: 12 | required: false 13 | repo_owner: 14 | required: true 15 | token: 16 | required: true 17 | 18 | outputs: 19 | release-notes: 20 | value: ${{ steps.get_release_notes.outputs.RELEASE_NOTES }} 21 | 22 | runs: 23 | using: composite 24 | 25 | steps: 26 | - uses: actions/github-script@v7 27 | id: get_release_notes 28 | with: 29 | result-encoding: string 30 | script: | 31 | const { data: pulls } = await github.rest.pulls.list({ 32 | owner: process.env.REPO_OWNER, 33 | repo: process.env.REPO_NAME, 34 | state: 'all', 35 | head: `${process.env.REPO_OWNER}:release/${process.env.VERSION}`, 36 | }); 37 | core.setOutput('RELEASE_NOTES', pulls[0].body); 38 | env: 39 | GITHUB_TOKEN: ${{ inputs.token }} 40 | REPO_OWNER: ${{ inputs.repo_owner }} 41 | REPO_NAME: ${{ inputs.repo_name }} 42 | VERSION: ${{ inputs.version }} 43 | -------------------------------------------------------------------------------- /.github/actions/get-version/action.yml: -------------------------------------------------------------------------------- 1 | name: Return the version extracted from the branch name 2 | 3 | # 4 | # Returns the version from the .version file. 5 | # 6 | # TODO: Remove once the common repo is public. 7 | # 8 | 9 | outputs: 10 | version: 11 | value: ${{ steps.get_version.outputs.VERSION }} 12 | 13 | runs: 14 | using: composite 15 | 16 | steps: 17 | - id: get_version 18 | shell: bash 19 | run: | 20 | VERSION=$(head -1 .version) 21 | echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT 22 | -------------------------------------------------------------------------------- /.github/actions/maven-publish/action.yml: -------------------------------------------------------------------------------- 1 | name: Publish release to Java 2 | 3 | inputs: 4 | java-version: 5 | required: true 6 | ossr-username: 7 | required: true 8 | ossr-token: 9 | required: true 10 | signing-key: 11 | required: true 12 | signing-password: 13 | required: true 14 | 15 | runs: 16 | using: composite 17 | 18 | steps: 19 | - name: Checkout code 20 | uses: actions/checkout@v4 21 | 22 | - name: Setup Java 23 | shell: bash 24 | run: | 25 | curl -s "https://get.sdkman.io" | bash 26 | source "/home/runner/.sdkman/bin/sdkman-init.sh" 27 | sdk list java 28 | sdk install java ${{ inputs.java-version }} && sdk default java ${{ inputs.java-version }} 29 | export JAVA_HOME=${SDKMAN_DIR}/candidates/java/current 30 | echo "JAVA_HOME is set to $JAVA_HOME" 31 | 32 | - uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 # pin@1.1.0 33 | env: 34 | JAVA_HOME: ${{ env.JAVA_HOME }} 35 | 36 | - name: Publish Java Packages to Maven 37 | shell: bash 38 | run: ./gradlew publish -PisSnapshot=false --stacktrace 39 | env: 40 | JAVA_HOME: ${{ env.JAVA_HOME }} 41 | MAVEN_USERNAME: ${{ inputs.ossr-username }} 42 | MAVEN_PASSWORD: ${{ inputs.ossr-token }} 43 | SIGNING_KEY: ${{ inputs.signing-key}} 44 | SIGNING_PASSWORD: ${{ inputs.signing-password}} -------------------------------------------------------------------------------- /.github/actions/release-create/action.yml: -------------------------------------------------------------------------------- 1 | name: Create a GitHub release 2 | 3 | # 4 | # Creates a GitHub release with the given version. 5 | # 6 | # TODO: Remove once the common repo is public. 7 | # 8 | 9 | inputs: 10 | token: 11 | required: true 12 | files: 13 | required: false 14 | name: 15 | required: true 16 | body: 17 | required: true 18 | tag: 19 | required: true 20 | commit: 21 | required: true 22 | draft: 23 | default: false 24 | required: false 25 | prerelease: 26 | default: false 27 | required: false 28 | fail_on_unmatched_files: 29 | default: true 30 | required: false 31 | 32 | runs: 33 | using: composite 34 | 35 | steps: 36 | - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 37 | with: 38 | body: ${{ inputs.body }} 39 | name: ${{ inputs.name }} 40 | tag_name: ${{ inputs.tag }} 41 | target_commitish: ${{ inputs.commit }} 42 | draft: ${{ inputs.draft }} 43 | prerelease: ${{ inputs.prerelease }} 44 | fail_on_unmatched_files: ${{ inputs.fail_on_unmatched_files }} 45 | files: ${{ inputs.files }} 46 | env: 47 | GITHUB_TOKEN: ${{ inputs.token }} 48 | -------------------------------------------------------------------------------- /.github/actions/rl-scanner/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Reversing Labs Scanner' 2 | description: 'Runs the Reversing Labs scanner on a specified artifact.' 3 | inputs: 4 | artifact-path: 5 | description: 'Path to the artifact to be scanned.' 6 | required: true 7 | version: 8 | description: 'Version of the artifact.' 9 | required: true 10 | 11 | runs: 12 | using: 'composite' 13 | steps: 14 | - name: Set up Python 15 | uses: actions/setup-python@v4 16 | with: 17 | python-version: '3.10' 18 | 19 | - name: Install Python dependencies 20 | shell: bash 21 | run: | 22 | pip install boto3 requests 23 | 24 | - name: Configure AWS credentials 25 | uses: aws-actions/configure-aws-credentials@v1 26 | with: 27 | role-to-assume: ${{ env.PRODSEC_TOOLS_ARN }} 28 | aws-region: us-east-1 29 | mask-aws-account-id: true 30 | 31 | - name: Install RL Wrapper 32 | shell: bash 33 | run: | 34 | 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" 35 | 36 | - name: Run RL Scanner 37 | shell: bash 38 | env: 39 | RLSECURE_LICENSE: ${{ env.RLSECURE_LICENSE }} 40 | RLSECURE_SITE_KEY: ${{ env.RLSECURE_SITE_KEY }} 41 | SIGNAL_HANDLER_TOKEN: ${{ env.SIGNAL_HANDLER_TOKEN }} 42 | PYTHONUNBUFFERED: 1 43 | run: | 44 | if [ ! -f "${{ inputs.artifact-path }}" ]; then 45 | echo "Artifact not found: ${{ inputs.artifact-path }}" 46 | exit 1 47 | fi 48 | 49 | rl-wrapper \ 50 | --artifact "${{ inputs.artifact-path }}" \ 51 | --name "${{ github.event.repository.name }}" \ 52 | --version "${{ inputs.version }}" \ 53 | --repository "${{ github.repository }}" \ 54 | --commit "${{ github.sha }}" \ 55 | --build-env "github_actions" \ 56 | --suppress_output 57 | 58 | # Check the outcome of the scanner 59 | if [ $? -ne 0 ]; then 60 | echo "RL Scanner failed." 61 | echo "scan-status=failed" >> $GITHUB_ENV 62 | exit 1 63 | else 64 | echo "RL Scanner passed." 65 | echo "scan-status=success" >> $GITHUB_ENV 66 | fi 67 | 68 | outputs: 69 | scan-status: 70 | description: 'The outcome of the scan process.' 71 | value: ${{ env.scan-status }} 72 | -------------------------------------------------------------------------------- /.github/actions/tag-exists/action.yml: -------------------------------------------------------------------------------- 1 | name: Return a boolean indicating if a tag already exists for the repository 2 | 3 | # 4 | # Returns a simple true/false boolean indicating whether the tag exists or not. 5 | # 6 | # TODO: Remove once the common repo is public. 7 | # 8 | 9 | inputs: 10 | token: 11 | required: true 12 | tag: 13 | required: true 14 | 15 | outputs: 16 | exists: 17 | description: 'Whether the tag exists or not' 18 | value: ${{ steps.tag-exists.outputs.EXISTS }} 19 | 20 | runs: 21 | using: composite 22 | 23 | steps: 24 | - id: tag-exists 25 | shell: bash 26 | run: | 27 | GET_API_URL="https://api.github.com/repos/${GITHUB_REPOSITORY}/git/ref/tags/${TAG_NAME}" 28 | http_status_code=$(curl -LI $GET_API_URL -o /dev/null -w '%{http_code}\n' -s -H "Authorization: token ${GITHUB_TOKEN}") 29 | if [ "$http_status_code" -ne "404" ] ; then 30 | echo "EXISTS=true" >> $GITHUB_OUTPUT 31 | else 32 | echo "EXISTS=false" >> $GITHUB_OUTPUT 33 | fi 34 | env: 35 | TAG_NAME: ${{ inputs.tag }} 36 | GITHUB_TOKEN: ${{ inputs.token }} 37 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 90 5 | 6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 7 | daysUntilClose: 7 8 | 9 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 10 | exemptLabels: [] 11 | 12 | # Set to true to ignore issues with an assignee (defaults to false) 13 | exemptAssignees: true 14 | 15 | # Label to use when marking as stale 16 | staleLabel: closed:stale 17 | 18 | # Comment to post when marking as stale. Set to `false` to disable 19 | markComment: > 20 | This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you have not received a response for our team (apologies for the delay) and this is still a blocker, please reply with additional information or just a ping. Thank you for your contribution! 🙇‍♂️ -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: auth0/auth0-java-mvc-common/build-and-test 2 | 3 | on: 4 | pull_request: 5 | merge_group: 6 | push: 7 | branches: ["master", "main", "v1"] 8 | 9 | jobs: 10 | gradle: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-java@v4 15 | with: 16 | distribution: temurin 17 | java-version: 8 18 | 19 | - name: Set up Gradle 20 | uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1 21 | 22 | - name: Test and Assemble and ApuDiff with Gradle 23 | run: ./gradlew assemble apiDiff check jacocoTestReport --continue --console=plain 24 | 25 | - uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d 26 | with: 27 | flags: unittests 28 | - uses: actions/upload-artifact@v4 29 | with: 30 | name: Reports 31 | path: lib/build/reports 32 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "master", "2.0.0-dev" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | schedule: 9 | - cron: "30 19 * * 6" 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: [ java ] 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v4 28 | 29 | - name: Initialize CodeQL 30 | uses: github/codeql-action/init@v3 31 | with: 32 | languages: ${{ matrix.language }} 33 | queries: +security-and-quality 34 | 35 | - name: Autobuild 36 | uses: github/codeql-action/autobuild@v3 37 | 38 | - name: Perform CodeQL Analysis 39 | uses: github/codeql-action/analyze@v3 40 | with: 41 | category: "/language:${{ matrix.language }}" 42 | -------------------------------------------------------------------------------- /.github/workflows/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | 8 | - package-ecosystem: "gradle" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | ignore: 13 | - dependency-name: "*" 14 | update-types: ["version-update:semver-major"] -------------------------------------------------------------------------------- /.github/workflows/java-release.yml: -------------------------------------------------------------------------------- 1 | name: Create Java and GitHub Release 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | java-version: 7 | required: true 8 | type: string 9 | secrets: 10 | ossr-username: 11 | required: true 12 | ossr-token: 13 | required: true 14 | signing-key: 15 | required: true 16 | signing-password: 17 | required: true 18 | github-token: 19 | required: true 20 | 21 | ### TODO: Replace instances of './.github/actions/' w/ `auth0/dx-sdk-actions/` and append `@latest` after the common `dx-sdk-actions` repo is made public. 22 | ### TODO: Also remove `get-prerelease`, `get-version`, `release-create`, `tag-create` and `tag-exists` actions from this repo's .github/actions folder once the repo is public. 23 | 24 | jobs: 25 | release: 26 | if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.merged && startsWith(github.event.pull_request.head.ref, 'release/')) 27 | runs-on: ubuntu-latest 28 | environment: release 29 | 30 | steps: 31 | # Checkout the code 32 | - uses: actions/checkout@v4 33 | with: 34 | fetch-depth: 0 35 | 36 | # Get the version from the branch name 37 | - id: get_version 38 | uses: ./.github/actions/get-version 39 | 40 | # Get the prerelease flag from the branch name 41 | - id: get_prerelease 42 | uses: ./.github/actions/get-prerelease 43 | with: 44 | version: ${{ steps.get_version.outputs.version }} 45 | 46 | # Get the release notes 47 | - id: get_release_notes 48 | uses: ./.github/actions/get-release-notes 49 | with: 50 | token: ${{ secrets.github-token }} 51 | version: ${{ steps.get_version.outputs.version }} 52 | repo_owner: ${{ github.repository_owner }} 53 | repo_name: ${{ github.event.repository.name }} 54 | 55 | # Check if the tag already exists 56 | - id: tag_exists 57 | uses: ./.github/actions/tag-exists 58 | with: 59 | tag: ${{ steps.get_version.outputs.version }} 60 | token: ${{ secrets.github-token }} 61 | 62 | # If the tag already exists, exit with an error 63 | - if: steps.tag_exists.outputs.exists == 'true' 64 | run: exit 1 65 | 66 | # Set JAVA_HOME here and pass it to subsequent steps 67 | - name: Set JAVA_HOME for Gradle 68 | run: echo "JAVA_HOME=/home/runner/.sdkman/candidates/java/current" >> $GITHUB_ENV # This ensures JAVA_HOME is set globally 69 | env: 70 | SDKMAN_DIR: /home/runner/.sdkman 71 | 72 | # Publish the release to Maven 73 | - uses: ./.github/actions/maven-publish 74 | with: 75 | java-version: ${{ inputs.java-version }} 76 | ossr-username: ${{ secrets.ossr-username }} 77 | ossr-token: ${{ secrets.ossr-token }} 78 | signing-key: ${{ secrets.signing-key }} 79 | signing-password: ${{ secrets.signing-password }} 80 | env: 81 | JAVA_HOME: ${{ env.JAVA_HOME }} 82 | 83 | # Create a release for the tag 84 | - uses: ./.github/actions/release-create 85 | with: 86 | token: ${{ secrets.github-token }} 87 | name: ${{ steps.get_version.outputs.version }} 88 | body: ${{ steps.get_release_notes.outputs.release-notes }} 89 | tag: ${{ steps.get_version.outputs.version }} 90 | commit: ${{ github.sha }} 91 | prerelease: ${{ steps.get_prerelease.outputs.prerelease }} 92 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create GitHub Release 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - closed 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: write 11 | id-token: write # This is required for requesting the JWT 12 | 13 | ### TODO: Replace instances of './.github/workflows/' w/ `auth0/dx-sdk-actions/workflows/` and append `@latest` after the common `dx-sdk-actions` repo is made public. 14 | ### TODO: Also remove `get-prerelease`, `get-release-notes`, `get-version`, `maven-publish`, `release-create`, and `tag-exists` actions from this repo's .github/actions folder once the repo is public. 15 | ### TODO: Also remove `java-release` workflow from this repo's .github/workflows folder once the repo is public. 16 | 17 | jobs: 18 | rl-scanner: 19 | uses: ./.github/workflows/rl-secure.yml 20 | with: 21 | java-version: 8 22 | artifact-name: "auth0-java-mvc-common.tgz" 23 | secrets: 24 | RLSECURE_LICENSE: ${{ secrets.RLSECURE_LICENSE }} 25 | RLSECURE_SITE_KEY: ${{ secrets.RLSECURE_SITE_KEY }} 26 | SIGNAL_HANDLER_TOKEN: ${{ secrets.SIGNAL_HANDLER_TOKEN }} 27 | PRODSEC_TOOLS_USER: ${{ secrets.PRODSEC_TOOLS_USER }} 28 | PRODSEC_TOOLS_TOKEN: ${{ secrets.PRODSEC_TOOLS_TOKEN }} 29 | PRODSEC_TOOLS_ARN: ${{ secrets.PRODSEC_TOOLS_ARN }} 30 | 31 | release: 32 | uses: ./.github/workflows/java-release.yml 33 | needs: rl-scanner 34 | with: 35 | java-version: 8.0.382-tem 36 | is-android: false 37 | secrets: 38 | ossr-username: ${{ secrets.OSSR_USERNAME }} 39 | ossr-token: ${{ secrets.OSSR_TOKEN }} 40 | signing-key: ${{ secrets.SIGNING_KEY }} 41 | signing-password: ${{ secrets.SIGNING_PASSWORD }} 42 | github-token: ${{ secrets.GITHUB_TOKEN }} 43 | -------------------------------------------------------------------------------- /.github/workflows/rl-secure.yml: -------------------------------------------------------------------------------- 1 | name: RL-Secure Workflow 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | java-version: 7 | required: true 8 | type: string 9 | artifact-name: 10 | required: true 11 | type: string 12 | secrets: 13 | RLSECURE_LICENSE: 14 | required: true 15 | RLSECURE_SITE_KEY: 16 | required: true 17 | SIGNAL_HANDLER_TOKEN: 18 | required: true 19 | PRODSEC_TOOLS_USER: 20 | required: true 21 | PRODSEC_TOOLS_TOKEN: 22 | required: true 23 | PRODSEC_TOOLS_ARN: 24 | required: true 25 | 26 | jobs: 27 | checkout-build-scan-only: 28 | if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.merged && startsWith(github.event.pull_request.head.ref, 'release/')) 29 | runs-on: ubuntu-latest 30 | outputs: 31 | scan-status: ${{ steps.rl-scan-conclusion.outcome }} 32 | 33 | steps: 34 | - name: Checkout code 35 | uses: actions/checkout@v4 36 | with: 37 | fetch-depth: 0 38 | 39 | - name: Set up Java 40 | uses: actions/setup-java@v4 41 | with: 42 | distribution: temurin 43 | java-version: ${{ inputs.java-version }} 44 | 45 | - name: Build with Gradle 46 | uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1 47 | with: 48 | arguments: assemble apiDiff check jacocoTestReport --continue --console=plain 49 | 50 | - name: Get Artifact Version 51 | id: get_version 52 | uses: ./.github/actions/get-version 53 | 54 | - name: Create tgz build artifact 55 | run: | 56 | tar -czvf ${{ inputs.artifact-name }} * 57 | 58 | - name: Run RL Scanner 59 | id: rl-scan-conclusion 60 | uses: ./.github/actions/rl-scanner 61 | with: 62 | artifact-path: "$(pwd)/${{ inputs.artifact-name }}" 63 | version: "${{ steps.get_version.outputs.version }}" 64 | env: 65 | RLSECURE_LICENSE: ${{ secrets.RLSECURE_LICENSE }} 66 | RLSECURE_SITE_KEY: ${{ secrets.RLSECURE_SITE_KEY }} 67 | SIGNAL_HANDLER_TOKEN: ${{ secrets.SIGNAL_HANDLER_TOKEN }} 68 | PRODSEC_TOOLS_USER: ${{ secrets.PRODSEC_TOOLS_USER }} 69 | PRODSEC_TOOLS_TOKEN: ${{ secrets.PRODSEC_TOOLS_TOKEN }} 70 | PRODSEC_TOOLS_ARN: ${{ secrets.PRODSEC_TOOLS_ARN }} 71 | 72 | - name: Output scan result 73 | run: echo "scan-status=${{ steps.rl-scan-conclusion.outcome }}" >> $GITHUB_ENV 74 | -------------------------------------------------------------------------------- /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | name: Semgrep 2 | 3 | on: 4 | pull_request: {} 5 | 6 | push: 7 | branches: ["master", "main"] 8 | 9 | schedule: 10 | - cron: '30 0 1,15 * *' 11 | 12 | jobs: 13 | semgrep: 14 | name: Scan 15 | runs-on: ubuntu-latest 16 | container: 17 | image: returntocorp/semgrep 18 | if: (github.actor != 'dependabot[bot]') 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - run: semgrep ci 23 | env: 24 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | target/ 3 | 4 | # Mobile Tools for Java (J2ME) 5 | .mtj.tmp/ 6 | 7 | # Package Files # 8 | *.jar 9 | *.war 10 | *.ear 11 | 12 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 13 | hs_err_pid* 14 | 15 | # macOS 16 | .DS_Store 17 | 18 | # IntelliJ 19 | .idea/ 20 | *.iml 21 | out/ 22 | 23 | ### Gradle ### 24 | .gradle 25 | **/build/ 26 | 27 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 28 | !gradle-wrapper.jar -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.shiprc: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "README.md": [], 4 | ".version": [], 5 | "build.gradle": ["version = \"{MAJOR}.{MINOR}.{PATCH}\""] 6 | }, 7 | "prefixVersion": false 8 | } -------------------------------------------------------------------------------- /.version: -------------------------------------------------------------------------------- 1 | 1.11.0 -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [1.11.0](https://github.com/auth0/auth0-java-mvc-common/tree/1.11.0) (2023-12-19) 4 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.10.0...1.11.0) 5 | 6 | **Changed** 7 | - [SDK-4670] Improved state handling errors [\#140](https://github.com/auth0/auth0-java-mvc-common/pull/140) ([jimmyjames](https://github.com/jimmyjames)) 8 | 9 | **Security** 10 | - Update dependencies for CVE-2023-3635 [\#137](https://github.com/auth0/auth0-java-mvc-common/pull/137) ([jimmyjames](https://github.com/jimmyjames)) 11 | 12 | ## [1.10.0](https://github.com/auth0/auth0-java-mvc-common/tree/1.10.0) (2023-07-18) 13 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.9.5...1.10.0) 14 | 15 | ** Added** 16 | - Support using organization name [\#132](https://github.com/auth0/auth0-java-mvc-common/pull/132) ([jimmyjames](https://github.com/jimmyjames)) 17 | 18 | ## [1.9.5](https://github.com/auth0/auth0-java-mvc-common/tree/1.9.5) (2023-03-11) 19 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.9.4...1.9.5) 20 | 21 | **Added** 22 | - Support Pushed Authorization Requests [\#128](https://github.com/auth0/auth0-java-mvc-common/pull/128) ([jimmyjames](https://github.com/jimmyjames)) 23 | - Support configurable cookie path [\#129](https://github.com/auth0/auth0-java-mvc-common/pull/129) ([jimmyjames](https://github.com/jimmyjames)) 24 | 25 | ## [1.9.4](https://github.com/auth0/auth0-java-mvc-common/tree/1.9.4) (2023-01-11) 26 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.9.3...1.9.4) 27 | 28 | This patch release does not contain any functional changes, but is being released using an updated signing key for verification as part of our commitment to best security practices. 29 | Please review [the README note for additional details.](https://github.com/auth0/auth0-java-mvc-common/blob/master/README.md) 30 | 31 | **Security** 32 | - Update dependencies [\#124](https://github.com/auth0/auth0-java-mvc-common/pull/124) ([jimmyjames](https://github.com/jimmyjames)) 33 | 34 | ## [1.9.3](https://github.com/auth0/auth0-java-mvc-common/tree/1.9.3) (2022-10-26) 35 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.9.2...1.9.3) 36 | 37 | **Changed** 38 | - Update to Gradle 6.9.2 and bump OSS plugin version [\#113](https://github.com/auth0/auth0-java-mvc-common/pull/113) ([jimmyjames](https://github.com/jimmyjames)) 39 | 40 | **Security** 41 | - Update dependencies [\#119](https://github.com/auth0/auth0-java-mvc-common/pull/119) ([jimmyjames](https://github.com/jimmyjames)) 42 | 43 | ## [1.9.2](https://github.com/auth0/auth0-java-mvc-common/tree/1.9.2) (2022-04-11) 44 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.9.1...1.9.2) 45 | 46 | **Security** 47 | - Update auth0-java dependency [\#107](https://github.com/auth0/auth0-java-mvc-common/pull/107) ([jimmyjames](https://github.com/jimmyjames)) 48 | - Update OkHttp version [\#106](https://github.com/auth0/auth0-java-mvc-common/pull/106) ([lbalmaceda](https://github.com/lbalmaceda)) 49 | 50 | ## [1.9.1](https://github.com/auth0/auth0-java-mvc-common/tree/1.9.1) (2022-03-30) 51 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.9.0...1.9.1) 52 | 53 | **Security** 54 | - Bump transitive jackson dependencies in auth0 libraries [\#104](https://github.com/auth0/auth0-java-mvc-common/pull/104) ([poovamraj](https://github.com/poovamraj)) 55 | 56 | ## [1.9.0](https://github.com/auth0/auth0-java-mvc-common/tree/1.9.0) (2022-03-14) 57 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.8.2...1.9.0) 58 | 59 | **Changed** 60 | - Update auth0 dependencies and address CVE-2020-36518 [\#102](https://github.com/auth0/auth0-java-mvc-common/pull/102) ([jimmyjames](https://github.com/jimmyjames)) 61 | 62 | **Security** 63 | - Update auth0 dependencies and address CVE-2020-36518 [\#102](https://github.com/auth0/auth0-java-mvc-common/pull/102) ([jimmyjames](https://github.com/jimmyjames)) 64 | 65 | ## [1.8.2](https://github.com/auth0/auth0-java-mvc-common/tree/1.8.2) (2022-01-19) 66 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.8.1...1.8.2) 67 | 68 | **Security** 69 | - Update auth0-java dependency [\#100](https://github.com/auth0/auth0-java-mvc-common/pull/100) ([poovamraj](https://github.com/poovamraj)) 70 | 71 | ## [1.8.1](https://github.com/auth0/auth0-java-mvc-common/tree/1.8.1) (2022-01-17) 72 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.8.0...1.8.1) 73 | 74 | **Security** 75 | - Update java jwt and jwks lib [\#98](https://github.com/auth0/auth0-java-mvc-common/pull/98) ([poovamraj](https://github.com/poovamraj)) 76 | 77 | ## [1.8.0](https://github.com/auth0/auth0-java-mvc-common/tree/1.8.0) (2021-07-06) 78 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.7.0...1.8.0) 79 | 80 | **Changed** 81 | - Update Auth0 libraries to latest [\#89](https://github.com/auth0/auth0-java-mvc-common/pull/89) ([lbalmaceda](https://github.com/lbalmaceda)) 82 | - Update OSS release plugin version [\#88](https://github.com/auth0/auth0-java-mvc-common/pull/88) ([lbalmaceda](https://github.com/lbalmaceda)) 83 | 84 | ## [1.7.0](https://github.com/auth0/auth0-java-mvc-common/tree/1.7.0) (2021-05-27) 85 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.6.0...1.7.0) 86 | 87 | **Added** 88 | - [SDK-2565] Enable network client configuration [\#86](https://github.com/auth0/auth0-java-mvc-common/pull/86) ([jimmyjames](https://github.com/jimmyjames)) 89 | 90 | ## [1.6.0](https://github.com/auth0/auth0-java-mvc-common/tree/1.6.0) (2021-03-26) 91 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.5.0...1.6.0) 92 | 93 | **Added** 94 | - Add support for Organizations feature [\#81](https://github.com/auth0/auth0-java-mvc-common/pull/81) ([jimmyjames](https://github.com/jimmyjames)) 95 | 96 | **Changed** 97 | - Request processor refactor to builder [\#80](https://github.com/auth0/auth0-java-mvc-common/pull/80) ([jimmyjames](https://github.com/jimmyjames)) 98 | 99 | ## [1.5.0](https://github.com/auth0/auth0-java-mvc-common/tree/1.5.0) (2021-02-09) 100 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.4.0...1.5.0) 101 | 102 | **Changed** 103 | - Update dependency versions [\#74](https://github.com/auth0/auth0-java-mvc-common/pull/74) ([jimmyjames](https://github.com/jimmyjames)) 104 | 105 | ## [1.4.0](https://github.com/auth0/auth0-java-mvc-common/tree/1.4.0) (2020-11-16) 106 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.3.0...1.4.0) 107 | 108 | **Added** 109 | - Target Java 8 [\#68](https://github.com/auth0/auth0-java-mvc-common/pull/68) ([jimmyjames](https://github.com/jimmyjames)) 110 | 111 | ## [1.3.0](https://github.com/auth0/auth0-java-mvc-common/tree/1.3.0) (2020-07-02) 112 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.2.0...1.3.0) 113 | 114 | **Added** 115 | - Allow cookies to always have the Secure attribute if configured [\#59](https://github.com/auth0/auth0-java-mvc-common/pull/59) ([jimmyjames](https://github.com/jimmyjames)) 116 | 117 | **Changed** 118 | - Update dependencies [\#60](https://github.com/auth0/auth0-java-mvc-common/pull/60) ([jimmyjames](https://github.com/jimmyjames)) 119 | 120 | ## [1.2.0](https://github.com/auth0/auth0-java-mvc-common/tree/1.2.0) (2020-01-10) 121 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.1.0...1.2.0) 122 | 123 | **Added** 124 | - Add SameSite Support [\#49](https://github.com/auth0/auth0-java-mvc-common/pull/49) ([jimmyjames](https://github.com/jimmyjames)) 125 | 126 | **Deprecated** 127 | - Deprecate request-only methods [\#50](https://github.com/auth0/auth0-java-mvc-common/pull/50) ([jimmyjames](https://github.com/jimmyjames)) 128 | 129 | **Removed** 130 | - Remove check that IAT claim must be in the future [\#52](https://github.com/auth0/auth0-java-mvc-common/pull/52) ([jimmyjames](https://github.com/jimmyjames)) 131 | 132 | ## [1.1.0](https://github.com/auth0/auth0-java-mvc-common/tree/1.1.0) (2019-10-29) 133 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.0.11...1.1.0) 134 | 135 | **Security** 136 | - Improved OIDC compliance [\#43](https://github.com/auth0/auth0-java-mvc-common/pull/43) ([lbalmaceda](https://github.com/lbalmaceda)) 137 | 138 | ## [1.0.11](https://github.com/auth0/auth0-java-mvc-common/tree/1.0.11) (2019-09-26) 139 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.0.10...1.0.11) 140 | 141 | **Security** 142 | - Update dependencies to address CVE [\#41](https://github.com/auth0/auth0-java-mvc-common/pull/41) ([jimmyjames](https://github.com/jimmyjames)) 143 | 144 | ## [1.0.10](https://github.com/auth0/auth0-java-mvc-common/tree/1.0.10) (2019-08-15) 145 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.0.9...1.0.10) 146 | 147 | **Security** 148 | - Update to latest Auth0 dependencies [\#39](https://github.com/auth0/auth0-java-mvc-common/pull/39) ([jimmyjames](https://github.com/jimmyjames)) 149 | 150 | ## [1.0.9](https://github.com/auth0/auth0-java-mvc-common/tree/1.0.9) (2019-07-03) 151 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.0.8...1.0.9) 152 | 153 | **Security** 154 | - Update to latest auth0-java [\#35](https://github.com/auth0/auth0-java-mvc-common/pull/35) ([jimmyjames](https://github.com/jimmyjames)) 155 | 156 | ## [1.0.8](https://github.com/auth0/auth0-java-mvc-common/tree/1.0.8) (2019-06-04) 157 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.0.7...1.0.8) 158 | 159 | **Fixed** 160 | - Rollback to fixed dependencies versions [\#33](https://github.com/auth0/auth0-java-mvc-common/pull/33) ([lbalmaceda](https://github.com/lbalmaceda)) 161 | 162 | ## [1.0.7](https://github.com/auth0/auth0-java-mvc-common/tree/1.0.7) (2019-05-23) 163 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.0.6...1.0.7) 164 | 165 | **Security** 166 | - Update dependencies [\#31](https://github.com/auth0/auth0-java-mvc-common/pull/31) ([lbalmaceda](https://github.com/lbalmaceda)) 167 | 168 | ## [1.0.6](https://github.com/auth0/auth0-java-mvc-common/tree/1.0.6) (2019-05-02) 169 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.0.5...1.0.6) 170 | 171 | **Fixed** 172 | - Allow telemetry to dynamically obtain the package version [\#28](https://github.com/auth0/auth0-java-mvc-common/pull/28) ([lbalmaceda](https://github.com/lbalmaceda)) 173 | 174 | ## [1.0.5](https://github.com/auth0/auth0-java-mvc-common/tree/1.0.5) (2019-04-17) 175 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.0.4...1.0.5) 176 | 177 | **Changed** 178 | - Bump dependencies [\#26](https://github.com/auth0/auth0-java-mvc-common/pull/26) ([lbalmaceda](https://github.com/lbalmaceda)) 179 | 180 | ## [1.0.4](https://github.com/auth0/auth0-java-mvc-common/tree/1.0.4) (2019-03-11) 181 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.0.3...1.0.4) 182 | 183 | **Fixed** 184 | - Revert auth0 dependencies scope to api (compile) [\#24](https://github.com/auth0/auth0-java-mvc-common/pull/24) ([lbalmaceda](https://github.com/lbalmaceda)) 185 | 186 | ## [1.0.3](https://github.com/auth0/auth0-java-mvc-common/tree/1.0.3) (2019-01-03) 187 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.0.2...1.0.3) 188 | 189 | **Security** 190 | - Bump Auth0 dependencies to fix security issue [\#20](https://github.com/auth0/auth0-java-mvc-common/pull/20) ([lbalmaceda](https://github.com/lbalmaceda)) 191 | 192 | ## [1.0.2](https://github.com/auth0/auth0-java-mvc-common/tree/1.0.2) (2018-10-24) 193 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.0.1...1.0.2) 194 | 195 | **Security** 196 | - Update dependencies [\#16](https://github.com/auth0/auth0-java-mvc-common/pull/16) ([lbalmaceda](https://github.com/lbalmaceda)) 197 | 198 | ## [1.0.1](https://github.com/auth0/auth0-java-mvc-common/tree/1.0.1) (2018-06-13) 199 | [Full Changelog](https://github.com/auth0/auth0-java-mvc-common/compare/1.0.0...1.0.1) 200 | 201 | **Security** 202 | - Use latest auth0-java, java-jwt and jwks-rsa libraries [\#11](https://github.com/auth0/auth0-java-mvc-common/pull/11) ([lbalmaceda](https://github.com/lbalmaceda)) 203 | 204 | ## [1.0.0](https://github.com/auth0/auth0-java-mvc-common/tree/1.0.0) (2017-05-24) 205 | 206 | Reworked the library to support both **Code Grant** and **Implicit Grant** authentication flows by using the latest [Auth0-Java](https://github.com/auth0/auth0-java/) SDK. 207 | 208 | The changes from v0 includes: 209 | 210 | - Simpler setup and configuration 211 | - Use of Auth0 Hosted Login page and OAuth 2.0 endpoints for Authentication 212 | - Support **Code Grant** and **Implicit Grant** flows. 213 | - Support Public Key Rotation when verifying Token signatures (Implicit Grant) 214 | -------------------------------------------------------------------------------- /EXAMPLES.md: -------------------------------------------------------------------------------- 1 | # Examples using auth0-java-mvc-common 2 | 3 | - [Including additional authorization parameters](#including-additional-authorization-parameters) 4 | - [Organizations](#organizations) 5 | - [Allowing clock skew for token validation](#allow-a-clock-skew-for-token-validation) 6 | - [Changing the OAuth response_type](#changing-the-oauth-response_type) 7 | - [HTTP logging](#http-logging) 8 | 9 | ## Including additional authorization parameters 10 | 11 | Parameters to send on the authorization request can be specified when configuring the authorization URL`: 12 | 13 | ```java 14 | String authorizeUrl = authController.buildAuthorizeUrl(request, response, "YOUR-REDIRECT-URL") 15 | .withAudience("https://myapi.me.auth0.com") 16 | .withScope("openid create:photos read:photos") 17 | .withParameter("name", "value") 18 | .build(); 19 | ``` 20 | 21 | ## Organizations 22 | 23 | [Organizations](https://auth0.com/docs/organizations) is a set of features that provide better support for developers who build and maintain SaaS and Business-to-Business (B2B) applications. 24 | 25 | Note that Organizations is currently only available to customers on our Enterprise and Startup subscription plans. 26 | 27 | ### Log in to an organization 28 | 29 | Log in to an organization by using `withOrganization()` when configuring the `AuthenticationController`, passing either the organization ID or organization name: 30 | 31 | ```java 32 | AuthenticationController controller = AuthenticationController.newBuilder("YOUR-AUTH0-DOMAIN", "YOUR-CLIENT-ID", "YOUR-CLIENT-SECRET") 33 | .withOrganization("{ORG_ID}") 34 | .build(); 35 | ``` 36 | 37 | When logging into an organization, this library will validate that the `org_id` or `org_name` claim of the ID Token matches the value configured. 38 | 39 | If no organization parameter was given to the authorization endpoint, but an `org_id` or `org_name` claim is present in the ID Token, then the claim should be validated by the application to ensure that the value received is expected or known. 40 | 41 | Normally, validating the issuer would be enough to ensure that the token was issued by Auth0, and this check is performed by this SDK. 42 | In the case of organizations, additional checks may be required so that the organization within an Auth0 tenant is expected. 43 | 44 | In particular, the `org_id` or `org_name` claim should be checked to ensure it is a value that is already known to the application. 45 | This could be validated against a known list of organizations, or perhaps checked in conjunction with the current request URL (e.g., the sub-domain may hint at what organization should be used to validate the ID Token). 46 | 47 | If the claim cannot be validated, then the application should deem the token invalid. 48 | The following example demonstrates this, using the [java-jwt](https://github.com/auth0/java-jwt) library: 49 | ```java 50 | // verify org_id using java-jwt, if needing to check against a list of valid organizations 51 | Tokens tokens = authenticationController.handle(req, res); 52 | String idToken = tokens.getIdToken(); 53 | List expectedOrgIds = Arrays.asList("ORG_ID_1", "ORG_ID_2"); 54 | DecodedJWT jwt = JWT.decode("TOKEN"); 55 | String jwtOrgId = jwt.getClaim("org_id").asString(); 56 | if (!expectedOrgIds.contains(jwtOrgId)) { 57 | // token invalid, do not trust 58 | } 59 | ``` 60 | 61 | For more information, please read [Work with Tokens and Organizations](https://auth0.com/docs/organizations/using-tokens) on Auth0 Docs. 62 | 63 | ### Accept user invitations 64 | 65 | Accept a user invitation by using `withInvitation()` when configuring the `AuthenticationController` (you must also specify the organization): 66 | 67 | ```java 68 | AuthenticationController controller = AuthenticationController.newBuilder("{DOMAIN}", "{CLIENT_ID}", "{CLIENT_SECRET}") 69 | .withOrganization("ORG_ID") 70 | .withInvitation("INVITATION_ID") 71 | .build(); 72 | ``` 73 | 74 | The ID of the invitation and organization are available as query parameters on the invitation URL, e.g., `https://your-domain.auth0.com/login?invitation={INVITATION_ID}&organization={ORG_ID}&organization_name={ORG_NAME}` 75 | 76 | ## Allow a clock skew for token validation 77 | 78 | During the authentication flow, the ID token is verified and validated to ensure it is secure. Time-based claims such as the time the token was issued at and the token's expiration are verified to ensure the token is valid. 79 | To accommodate potential small differences in system clocks, this library allows a default of **60 seconds** of clock skew. 80 | 81 | You can customize the clock skew as shown below: 82 | 83 | ```java 84 | AuthenticationController authController = AuthenticationController.newBuilder("YOUR-DOMAIN", "YOUR-CLIENT-ID", "YOUR-CLIENT-SECRET") 85 | .withClockSkew(60 * 2) // 2 minutes 86 | .build(); 87 | ``` 88 | 89 | ## Changing the OAuth response_type 90 | 91 | By default, this library uses the `code` response_type. This can be changed by specifying the desired `response_type` on the `AuthenticationController#Builder`: 92 | 93 | ```java 94 | AuthenticationController authController = AuthenticationController.newBuilder("YOUR-AUTH0-DOMAIN", "YOUR-CLIENT-ID", "YOUR-CLIENT-SECRET") 95 | .withResponseType("id_token code") 96 | .build(); 97 | ``` 98 | 99 | ## HTTP logging 100 | 101 | Once you have created the instance of the `AuthenticationController`, you can enable HTTP logging for all Requests and Responses to debug a specific endpoint. 102 | **This will log everything including sensitive information** - do not use it in a production environment. 103 | 104 | ```java 105 | authController.setLoggingEnabled(true); 106 | ``` 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Auth0, Inc. (http://auth0.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Auth0 SDK to add authentication to your Java Servlet applications.](https://cdn.auth0.com/website/sdks/banners/auth0-java-mvc-common-banner.png) 2 | 3 | ![Build Status](https://img.shields.io/github/checks-status/auth0/auth0-java-mvc-common/master) 4 | [![Coverage Status](https://img.shields.io/codecov/c/github/auth0/auth0-java-mvc-common.svg?style=flat-square)](https://codecov.io/github/auth0/auth0-java-mvc-common) 5 | [![License](http://img.shields.io/:license-mit-blue.svg?style=flat)](https://doge.mit-license.org/) 6 | [![Maven Central](https://img.shields.io/maven-central/v/com.auth0/mvc-auth-commons.svg?style=flat-square)](https://mvnrepository.com/artifact/com.auth0/mvc-auth-commons) 7 | [![javadoc](https://javadoc.io/badge2/com.auth0/auth0-java-mvc-common/javadoc.svg)](https://javadoc.io/doc/com.auth0/mvc-auth-commons) 8 | 9 | > **Note** 10 | > As part of our ongoing commitment to best security practices, we have rotated the signing keys used to sign previous releases of this SDK. As a result, new patch builds have been released using the new signing key. Please upgrade at your earliest convenience. 11 | > 12 | > While this change won't affect most developers, if you have implemented a dependency signature validation step in your build process, you may notice a warning that past releases can't be verified. This is expected, and a result of the key rotation process. Updating to the latest version will resolve this for you. 13 | 14 | :books: [Documentation](#documentation) - :rocket: [Getting Started](#getting-started) - :computer: [API Reference](#api-reference) :speech_balloon: [Feedback](#feedback) 15 | 16 | ## Documentation 17 | 18 | - [Quickstart](https://auth0.com/docs/quickstart/webapp/java) - our interactive guide for quickly adding login, logout and user information to a Java Servlet application using Auth0. 19 | - [Sample App](https://github.com/auth0-samples/auth0-servlet-sample/tree/master/01-Login) - a sample Java Servlet application integrated with Auth0. 20 | - [Examples](./EXAMPLES.md) - code samples for common scenarios. 21 | - [Docs site](https://www.auth0.com/docs) - explore our docs site and learn more about Auth0. 22 | 23 | ## Getting Started 24 | 25 | ### Requirements 26 | 27 | Java 8 or above and `javax.servlet` version 3. 28 | 29 | > If you are using Spring, we recommend leveraging Spring's OIDC and OAuth2 support, as demonstrated by the [Spring Boot Quickstart](https://auth0.com/docs/quickstart/webapp/java-spring-boot). 30 | 31 | ### Installation 32 | 33 | Add the dependency via Maven: 34 | 35 | ```xml 36 | 37 | com.auth0 38 | mvc-auth-commons 39 | 1.11.0 40 | 41 | ``` 42 | 43 | or Gradle: 44 | 45 | ```gradle 46 | implementation 'com.auth0:mvc-auth-commons:1.11.0' 47 | ``` 48 | 49 | ### Configure Auth0 50 | 51 | Create a **Regular Web Application** in the [Auth0 Dashboard](https://manage.auth0.com/#/applications). Verify that the "Token Endpoint Authentication Method" is set to `POST`. 52 | 53 | Next, configure the callback and logout URLs for your application under the "Application URIs" section of the "Settings" page: 54 | 55 | - **Allowed Callback URLs**: The URL of your application where Auth0 will redirect to during authentication, e.g., `http://localhost:3000/callback`. 56 | - **Allowed Logout URLs**: The URL of your application where Auth0 will redirect to after user logout, e.g., `http://localhost:3000/login`. 57 | 58 | Note the **Domain**, **Client ID**, and **Client Secret**. These values will be used later. 59 | 60 | ### Add login to your application 61 | 62 | Create a new `AuthenticationController` using your Auth0 domain, and Auth0 application client ID and secret. 63 | Configure the builder with a `JwkProvider` for your Auth0 domain. 64 | 65 | ```java 66 | public class AuthenticationControllerProvider { 67 | private String domain = "YOUR-AUTH0-DOMAIN"; 68 | private String clientId = "YOUR-CLIENT-ID"; 69 | private String clientSecret = "YOUR-CLIENT-SECRET"; 70 | 71 | private AuthenticationController authenticationController; 72 | 73 | static { 74 | JwkProvider jwkProvider = new JwkProviderBuilder("YOUR-AUTH0-DOMAIN").build(); 75 | authenticationController = AuthenticationController.newBuilder(domain, clientId, clientSecret) 76 | .withJwkProvider(jwkProvider) 77 | .build(); 78 | } 79 | 80 | public getInstance() { 81 | return authenticationController; 82 | } 83 | } 84 | ``` 85 | 86 | > Note: The `AuthenticationController.Builder` is not to be reused, and an `IllegalStateException` will be thrown if `build()` is called more than once. 87 | 88 | Redirect users to the Auth0 login page using the `AuthenticationController`: 89 | 90 | ```java 91 | @WebServlet(urlPatterns = {"/login"}) 92 | public class LoginServlet extends HttpServlet { 93 | 94 | @Override 95 | protected void doGet(final HttpServletRequest req, final HttpServletResponse res) throws ServletException, IOException { 96 | // Where your application will handle the authoriztion callback 97 | String redirectUrl = "http://localhost:3000/callback"; 98 | 99 | String authorizeUrl = AuthenticationControllerProvider 100 | .getInstance() 101 | .buildAuthorizeUrl(req, res, redirectUrl) 102 | .build(); 103 | res.sendRedirect(authorizeUrl); 104 | } 105 | } 106 | ``` 107 | 108 | Finally, complete the authentication and obtain the tokens by calling `handle()` on the `AuthenticationController`. 109 | 110 | ```java 111 | @WebServlet(urlPatterns = {"/callback"}) 112 | public class CallbackServlet extends HttpServlet { 113 | 114 | @Override 115 | public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { 116 | try { 117 | // authentication complete; the tokens can be stored as needed 118 | Tokens tokens = AuthenticationControllerProvider 119 | .getInstance() 120 | .handle(req, res); 121 | res.sendRedirect("URL-AFTER-AUTHENTICATED"); 122 | } catch (IdentityVerificationException e) { 123 | // handle authentication error 124 | } 125 | } 126 | } 127 | ``` 128 | 129 | That's it! You have authenticated the user using Auth0. 130 | 131 | ## API Reference 132 | 133 | - [JavaDocs](https://javadoc.io/doc/com.auth0/mvc-auth-commons) 134 | 135 | ## Feedback 136 | 137 | ### Contributing 138 | 139 | We appreciate feedback and contribution to this repo! Before you get started, please see the following: 140 | 141 | - [Auth0's general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md) 142 | - [Auth0's code of conduct guidelines](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md) 143 | 144 | ### Raise an issue 145 | To provide feedback or report a bug, [please raise an issue on our issue tracker](https://github.com/auth0/auth0-java-mvc-common/issues). 146 | 147 | ### Vulnerability Reporting 148 | Please do not report security vulnerabilities on the public Github issue tracker. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues. 149 | 150 | --- 151 | 152 |

153 | 154 | 155 | 156 | Auth0 Logo 157 | 158 |

159 |

Auth0 is an easy to implement, adaptable authentication and authorization platform. To learn more checkout Why Auth0?

160 |

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

162 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | 6 | dependencies { 7 | // https://github.com/melix/japicmp-gradle-plugin/issues/36 8 | classpath 'com.google.guava:guava:31.1-jre' 9 | } 10 | } 11 | 12 | plugins { 13 | id 'java' 14 | id 'java-library' 15 | id 'jacoco' 16 | id 'me.champeau.gradle.japicmp' version '0.4.1' 17 | } 18 | 19 | repositories { 20 | mavenCentral() 21 | } 22 | 23 | apply from: rootProject.file('gradle/versioning.gradle') 24 | 25 | version = getVersionFromFile() 26 | group = GROUP 27 | logger.lifecycle("Using version ${version} for ${name} group $group") 28 | 29 | import me.champeau.gradle.japicmp.JapicmpTask 30 | 31 | project.afterEvaluate { 32 | def versions = project.ext.testInJavaVersions 33 | for (pluginJavaTestVersion in versions) { 34 | def taskName = "testInJava-${pluginJavaTestVersion}" 35 | tasks.register(taskName, Test) { 36 | def versionToUse = taskName.split("-").getAt(1) as Integer 37 | description = "Runs unit tests on Java version ${versionToUse}." 38 | project.logger.quiet("Test will be running in ${versionToUse}") 39 | group = 'verification' 40 | javaLauncher.set(javaToolchains.launcherFor { 41 | languageVersion = JavaLanguageVersion.of(versionToUse) 42 | }) 43 | shouldRunAfter(tasks.named('test')) 44 | } 45 | tasks.named('check') { 46 | dependsOn(taskName) 47 | } 48 | } 49 | 50 | project.configure(project) { 51 | def baselineVersion = project.ext.baselineCompareVersion 52 | task('apiDiff', type: JapicmpTask, dependsOn: 'jar') { 53 | oldClasspath.from(files(getBaselineJar(project, baselineVersion))) 54 | newClasspath.from(files(jar.archiveFile)) 55 | onlyModified = true 56 | failOnModification = true 57 | ignoreMissingClasses = true 58 | htmlOutputFile = file("$buildDir/reports/apiDiff/apiDiff.html") 59 | txtOutputFile = file("$buildDir/reports/apiDiff/apiDiff.txt") 60 | doLast { 61 | project.logger.quiet("Comparing against baseline version ${baselineVersion}") 62 | } 63 | } 64 | } 65 | } 66 | 67 | private static File getBaselineJar(Project project, String baselineVersion) { 68 | // Use detached configuration: https://github.com/square/okhttp/blob/master/build.gradle#L270 69 | def group = project.group 70 | try { 71 | def baseline = "${project.group}:${project.name}:$baselineVersion" 72 | project.group = 'virtual_group_for_japicmp' 73 | def dependency = project.dependencies.create(baseline + "@jar") 74 | return project.configurations.detachedConfiguration(dependency).files.find { 75 | it.name == "${project.name}-${baselineVersion}.jar" 76 | } 77 | } finally { 78 | project.group = group 79 | } 80 | } 81 | 82 | ext { 83 | baselineCompareVersion = '1.5.0' 84 | testInJavaVersions = [8, 11, 17, 21] 85 | } 86 | 87 | jacocoTestReport { 88 | reports { 89 | xml.enabled = true 90 | html.enabled = true 91 | } 92 | } 93 | 94 | java { 95 | toolchain { 96 | languageVersion = JavaLanguageVersion.of(8) 97 | } 98 | // Needed because of broken gradle metadata, see https://github.com/google/guava/issues/6612#issuecomment-1614992368 99 | sourceSets.all { 100 | configurations.getByName(runtimeClasspathConfigurationName) { 101 | attributes.attribute(Attribute.of("org.gradle.jvm.environment", String), "standard-jvm") 102 | } 103 | configurations.getByName(compileClasspathConfigurationName) { 104 | attributes.attribute(Attribute.of("org.gradle.jvm.environment", String), "standard-jvm") 105 | } 106 | } 107 | } 108 | 109 | compileJava { 110 | sourceCompatibility '1.8' 111 | targetCompatibility '1.8' 112 | } 113 | 114 | test { 115 | useJUnitPlatform() 116 | testLogging { 117 | events "skipped", "failed" 118 | exceptionFormat "short" 119 | } 120 | } 121 | 122 | dependencies { 123 | implementation 'javax.servlet:javax.servlet-api:3.1.0' 124 | implementation 'org.apache.commons:commons-lang3:3.12.0' 125 | implementation 'com.google.guava:guava-annotations:r03' 126 | implementation 'commons-codec:commons-codec:1.15' 127 | 128 | api 'com.auth0:auth0:1.45.1' 129 | api 'com.auth0:java-jwt:3.19.4' 130 | api 'com.auth0:jwks-rsa:0.22.1' 131 | 132 | testImplementation 'org.bouncycastle:bcprov-jdk15on:1.64' 133 | testImplementation 'org.hamcrest:java-hamcrest:2.0.0.0' 134 | testImplementation 'org.hamcrest:hamcrest-core:1.3' 135 | testImplementation 'org.mockito:mockito-core:2.8.9' 136 | testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1' 137 | testImplementation 'org.springframework:spring-test:4.3.14.RELEASE' 138 | testImplementation 'com.squareup.okhttp3:okhttp:4.11.0' 139 | } 140 | 141 | apply from: rootProject.file('gradle/maven-publish.gradle') 142 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | GROUP=com.auth0 2 | POM_ARTIFACT_ID=mvc-auth-commons 3 | 4 | POM_NAME=auth0-java-mvc-common 5 | POM_DESCRIPTION=Java library that simplifies the use of Auth0 for server-side MVC web apps 6 | POM_PACKAGING=jar 7 | 8 | POM_URL=https://github.com/auth0/auth0-java-mvc-common 9 | POM_SCM_URL=https://github.com/auth0/auth0-java-mvc-common 10 | 11 | POM_SCM_CONNECTION=scm:git:https://github.com/auth0/auth0-java-mvc-common.git 12 | POM_SCM_DEV_CONNECTION=scm:git:https://github.com/auth0/auth0-java-mvc-common.git 13 | 14 | POM_LICENCE_NAME=The MIT License (MIT) 15 | POM_LICENCE_URL=https://raw.githubusercontent.com/auth0/java-jwt/master/LICENSE 16 | POM_LICENCE_DIST=repo 17 | 18 | POM_DEVELOPER_ID=auth0 19 | POM_DEVELOPER_NAME=Auth0 20 | POM_DEVELOPER_EMAIL=oss@auth0.com -------------------------------------------------------------------------------- /gradle/maven-publish.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven-publish' 2 | apply plugin: 'signing' 3 | 4 | task('sourcesJar', type: Jar, dependsOn: classes) { 5 | archiveClassifier = 'sources' 6 | from sourceSets.main.allSource 7 | } 8 | 9 | task('javadocJar', type: Jar, dependsOn: javadoc) { 10 | archiveClassifier = 'javadoc' 11 | from javadoc.getDestinationDir() 12 | } 13 | tasks.withType(Javadoc).configureEach { 14 | javadocTool = javaToolchains.javadocToolFor { 15 | // Use latest JDK for javadoc generation 16 | languageVersion = JavaLanguageVersion.of(17) 17 | } 18 | } 19 | 20 | javadoc { 21 | // Specify the Java version that the project will use 22 | options.addStringOption('-release', "8") 23 | } 24 | artifacts { 25 | archives sourcesJar, javadocJar 26 | } 27 | 28 | 29 | final releaseRepositoryUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 30 | final snapshotRepositoryUrl = "https://oss.sonatype.org/content/repositories/snapshots/" 31 | 32 | publishing { 33 | publications { 34 | mavenJava(MavenPublication) { 35 | 36 | groupId = GROUP 37 | artifactId = POM_ARTIFACT_ID 38 | version = getVersionName() 39 | 40 | artifact("$buildDir/libs/${project.name}-${version}.jar") 41 | artifact sourcesJar 42 | artifact javadocJar 43 | 44 | pom { 45 | name = POM_NAME 46 | packaging = POM_PACKAGING 47 | description = POM_DESCRIPTION 48 | url = POM_URL 49 | 50 | licenses { 51 | license { 52 | name = POM_LICENCE_NAME 53 | url = POM_LICENCE_URL 54 | distribution = POM_LICENCE_DIST 55 | } 56 | } 57 | 58 | developers { 59 | developer { 60 | id = POM_DEVELOPER_ID 61 | name = POM_DEVELOPER_NAME 62 | email = POM_DEVELOPER_EMAIL 63 | } 64 | } 65 | 66 | scm { 67 | url = POM_SCM_URL 68 | connection = POM_SCM_CONNECTION 69 | developerConnection = POM_SCM_DEV_CONNECTION 70 | } 71 | 72 | pom.withXml { 73 | def dependenciesNode = asNode().appendNode('dependencies') 74 | 75 | project.configurations.implementation.allDependencies.each { 76 | def dependencyNode = dependenciesNode.appendNode('dependency') 77 | dependencyNode.appendNode('groupId', it.group) 78 | dependencyNode.appendNode('artifactId', it.name) 79 | dependencyNode.appendNode('version', it.version) 80 | } 81 | } 82 | } 83 | } 84 | } 85 | repositories { 86 | maven { 87 | name = "sonatype" 88 | url = version.endsWith('SNAPSHOT') ? snapshotRepositoryUrl : releaseRepositoryUrl 89 | credentials { 90 | username = System.getenv("MAVEN_USERNAME") 91 | password = System.getenv("MAVEN_PASSWORD") 92 | } 93 | } 94 | } 95 | } 96 | 97 | signing { 98 | def signingKey = System.getenv("SIGNING_KEY") 99 | def signingPassword = System.getenv("SIGNING_PASSWORD") 100 | useInMemoryPgpKeys(signingKey, signingPassword) 101 | 102 | sign publishing.publications.mavenJava 103 | } 104 | 105 | javadoc { 106 | if(JavaVersion.current().isJava9Compatible()) { 107 | options.addBooleanOption('html5', true) 108 | } 109 | } 110 | 111 | tasks.named('publish').configure { 112 | dependsOn tasks.named('assemble') 113 | } -------------------------------------------------------------------------------- /gradle/versioning.gradle: -------------------------------------------------------------------------------- 1 | def getVersionFromFile() { 2 | def versionFile = rootProject.file('.version') 3 | return versionFile.text.readLines().first().trim() 4 | } 5 | 6 | def isSnapshot() { 7 | return hasProperty('isSnapshot') ? isSnapshot.toBoolean() : true 8 | } 9 | 10 | def getVersionName() { 11 | return isSnapshot() ? project.version+"-SNAPSHOT" : project.version 12 | } 13 | 14 | ext { 15 | getVersionName = this.&getVersionName 16 | getVersionFromFile = this.&getVersionFromFile 17 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0/auth0-java-mvc-common/1fe03a1ae2071d2cbb454a84660270e53a0967a7/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.2-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /opslevel.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 1 3 | repository: 4 | owner: dx_sdks 5 | tier: 6 | tags: 7 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | 7 | rootProject.name = 'mvc-auth-commons' 8 | -------------------------------------------------------------------------------- /src/main/java/com/auth0/AlgorithmNameVerifier.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | @SuppressWarnings("unused") 4 | class AlgorithmNameVerifier extends SignatureVerifier { 5 | 6 | AlgorithmNameVerifier() { 7 | //Must only allow supported algorithms and never "none" algorithm 8 | super(null, "HS256", "RS256"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/auth0/AsymmetricSignatureVerifier.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | import com.auth0.jwk.Jwk; 4 | import com.auth0.jwk.JwkException; 5 | import com.auth0.jwk.JwkProvider; 6 | import com.auth0.jwt.JWT; 7 | import com.auth0.jwt.JWTVerifier; 8 | import com.auth0.jwt.algorithms.Algorithm; 9 | import com.auth0.jwt.interfaces.RSAKeyProvider; 10 | 11 | import java.security.interfaces.RSAPrivateKey; 12 | import java.security.interfaces.RSAPublicKey; 13 | 14 | @SuppressWarnings("unused") 15 | class AsymmetricSignatureVerifier extends SignatureVerifier { 16 | 17 | AsymmetricSignatureVerifier(JwkProvider jwkProvider) { 18 | super(createJWTVerifier(jwkProvider), "RS256"); 19 | } 20 | 21 | private static JWTVerifier createJWTVerifier(final JwkProvider jwkProvider) { 22 | Algorithm alg = Algorithm.RSA256(new RSAKeyProvider() { 23 | @Override 24 | public RSAPublicKey getPublicKeyById(String keyId) { 25 | try { 26 | Jwk jwk = jwkProvider.get(keyId); 27 | return (RSAPublicKey) jwk.getPublicKey(); 28 | } catch (JwkException ignored) { 29 | // JwkException handled by Algorithm verify implementation from java-jwt 30 | } 31 | return null; 32 | } 33 | 34 | @Override 35 | public RSAPrivateKey getPrivateKey() { 36 | //NO-OP 37 | return null; 38 | } 39 | 40 | @Override 41 | public String getPrivateKeyId() { 42 | //NO-OP 43 | return null; 44 | } 45 | }); 46 | return JWT.require(alg) 47 | .ignoreIssuedAt() 48 | .build(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/auth0/AuthCookie.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | import org.apache.commons.lang3.Validate; 4 | 5 | import java.io.UnsupportedEncodingException; 6 | import java.net.URLEncoder; 7 | import java.nio.charset.StandardCharsets; 8 | 9 | /** 10 | * Represents a cookie to be used for transfer of authentiction-based data such as state and nonce. 11 | * 12 | * This is an internal replacement for the Java Servlet Cookie implementation, so that it can set the SameSite 13 | * attribute (not yet supported in Java Servlet API). It is intended to be used via the Set-Cookie header. 14 | * 15 | * By default, cookies will have the HttpOnly attribute set, and a Max-Age of 600 seconds (10 minutes). 16 | */ 17 | class AuthCookie { 18 | 19 | private final static int MAX_AGE_SECONDS = 600; // 10 minutes 20 | 21 | private final String key; 22 | private final String value; 23 | private boolean secure; 24 | private SameSite sameSite; 25 | private String cookiePath; 26 | 27 | /** 28 | * Create a new instance. 29 | * 30 | * @param key The cookie key 31 | * @param value The cookie value 32 | */ 33 | AuthCookie(String key, String value) { 34 | Validate.notNull(key, "Key must not be null"); 35 | Validate.notNull(value, "Value must not be null"); 36 | 37 | this.key = key; 38 | this.value = value; 39 | } 40 | 41 | void setPath(String path) { 42 | this.cookiePath = path; 43 | } 44 | 45 | /** 46 | * Sets whether the Secure attribute should be set on the cookie. False by default. 47 | * 48 | * @param secure whether the Cookie should have the Secure attribute or not. 49 | */ 50 | void setSecure(boolean secure) { 51 | this.secure = secure; 52 | } 53 | 54 | /** 55 | * Sets the value of the SameSite attribute. Unless set, no SameSite attribute will be set on the cookie. 56 | * 57 | * @param sameSite The value of the SameSite attribute. 58 | */ 59 | void setSameSite(SameSite sameSite) { 60 | this.sameSite = sameSite; 61 | } 62 | 63 | /** 64 | * Builds and returns a string representing this cookie instance, to be used as the value of a "Set-Cookie" header. 65 | * The cookie key and value will be URL-encoded using the UTF-8 character set. 66 | * 67 | * @throws AssertionError if the UTF-8 character set is not supported on the running JVM. 68 | * @return the value of this cookie as a string to be used as the value of a "Set-Cookie" header. 69 | */ 70 | String buildHeaderString() { 71 | String baseCookieString = String.format("%s=%s; HttpOnly; Max-Age=%d", encode(key), encode(value), MAX_AGE_SECONDS); 72 | if (cookiePath != null) { 73 | baseCookieString = baseCookieString.concat(String.format("; Path=%s", cookiePath)); 74 | } 75 | if (sameSite != null) { 76 | baseCookieString = baseCookieString.concat(String.format("; SameSite=%s", encode(sameSite.getValue()))); 77 | } 78 | if (secure) { 79 | baseCookieString = baseCookieString.concat("; Secure"); 80 | } 81 | return baseCookieString; 82 | } 83 | 84 | private static String encode(String valueToEncode) { 85 | try { 86 | return URLEncoder.encode(valueToEncode, StandardCharsets.UTF_8.name()); 87 | } catch (UnsupportedEncodingException e) { 88 | throw new AssertionError("UTF-8 character set not supported", e.getCause()); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/auth0/AuthenticationController.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | import com.auth0.client.HttpOptions; 4 | import com.auth0.client.auth.AuthAPI; 5 | import com.auth0.jwk.JwkProvider; 6 | import com.auth0.net.Telemetry; 7 | import com.google.common.annotations.VisibleForTesting; 8 | import org.apache.commons.lang3.Validate; 9 | 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | 13 | 14 | /** 15 | * Base Auth0 Authenticator class. 16 | * Allows to easily authenticate using the Auth0 Hosted Login Page. 17 | */ 18 | @SuppressWarnings({"WeakerAccess", "UnusedReturnValue", "SameParameterValue"}) 19 | public class AuthenticationController { 20 | 21 | private final RequestProcessor requestProcessor; 22 | 23 | /** 24 | * Called from the Builder but also from tests in order to pass the mock. 25 | */ 26 | @VisibleForTesting 27 | AuthenticationController(RequestProcessor requestProcessor) { 28 | this.requestProcessor = requestProcessor; 29 | } 30 | 31 | @VisibleForTesting 32 | RequestProcessor getRequestProcessor() { 33 | return requestProcessor; 34 | } 35 | 36 | /** 37 | * Create a new {@link Builder} instance to configure the {@link AuthenticationController} response type and algorithm used on the verification. 38 | * By default it will request response type 'code' and later perform the Code Exchange, but if the response type is changed to 'token' it will handle 39 | * the Implicit Grant using the HS256 algorithm with the Client Secret as secret. 40 | * 41 | * @param domain the Auth0 domain 42 | * @param clientId the Auth0 application's client id 43 | * @param clientSecret the Auth0 application's client secret 44 | * @return a new Builder instance ready to configure 45 | */ 46 | public static Builder newBuilder(String domain, String clientId, String clientSecret) { 47 | return new Builder(domain, clientId, clientSecret); 48 | } 49 | 50 | 51 | public static class Builder { 52 | private static final String RESPONSE_TYPE_CODE = "code"; 53 | 54 | private final String domain; 55 | private final String clientId; 56 | private final String clientSecret; 57 | private String responseType; 58 | private JwkProvider jwkProvider; 59 | private Integer clockSkew; 60 | private Integer authenticationMaxAge; 61 | private boolean useLegacySameSiteCookie; 62 | private String organization; 63 | private String invitation; 64 | private HttpOptions httpOptions; 65 | private String cookiePath; 66 | 67 | Builder(String domain, String clientId, String clientSecret) { 68 | Validate.notNull(domain); 69 | Validate.notNull(clientId); 70 | Validate.notNull(clientSecret); 71 | 72 | this.domain = domain; 73 | this.clientId = clientId; 74 | this.clientSecret = clientSecret; 75 | this.responseType = RESPONSE_TYPE_CODE; 76 | this.useLegacySameSiteCookie = true; 77 | } 78 | 79 | /** 80 | * Customize certain aspects of the underlying HTTP client networking library, such as timeouts and proxy configuration. 81 | * 82 | * @param httpOptions a non-null {@code HttpOptions} 83 | * @return this same builder instance. 84 | */ 85 | public Builder withHttpOptions(HttpOptions httpOptions) { 86 | Validate.notNull(httpOptions); 87 | this.httpOptions = httpOptions; 88 | return this; 89 | } 90 | 91 | /** 92 | * Specify that transient authentication-based cookies such as state and nonce are created with the specified 93 | * {@code Path} cookie attribute. 94 | * 95 | * @param cookiePath the path to set on the cookie. 96 | * @return this builder instance. 97 | */ 98 | public Builder withCookiePath(String cookiePath) { 99 | Validate.notNull(cookiePath); 100 | this.cookiePath = cookiePath; 101 | return this; 102 | } 103 | 104 | /** 105 | * Change the response type to request in the Authorization step. Default value is 'code'. 106 | * 107 | * @param responseType the response type to request. Any combination of 'code', 'token' and 'id_token' but 'token id_token' is allowed, using a space as separator. 108 | * @return this same builder instance. 109 | */ 110 | public Builder withResponseType(String responseType) { 111 | Validate.notNull(responseType); 112 | this.responseType = responseType.trim().toLowerCase(); 113 | return this; 114 | } 115 | 116 | /** 117 | * Sets the Jwk Provider that will return the Public Key required to verify the token in case of Implicit Grant flows. 118 | * This is required if the Auth0 Application is signing the tokens with the RS256 algorithm. 119 | * 120 | * @param jwkProvider a valid Jwk provider. 121 | * @return this same builder instance. 122 | */ 123 | public Builder withJwkProvider(JwkProvider jwkProvider) { 124 | Validate.notNull(jwkProvider); 125 | this.jwkProvider = jwkProvider; 126 | return this; 127 | } 128 | 129 | /** 130 | * Sets the clock-skew or leeway value to use in the ID Token verification. The value must be in seconds. 131 | * Defaults to 60 seconds. 132 | * 133 | * @param clockSkew the clock-skew to use for ID Token verification, in seconds. 134 | * @return this same builder instance. 135 | */ 136 | public Builder withClockSkew(Integer clockSkew) { 137 | Validate.notNull(clockSkew); 138 | this.clockSkew = clockSkew; 139 | return this; 140 | } 141 | 142 | /** 143 | * Sets the allowable elapsed time in seconds since the last time user was authenticated. 144 | * By default there is no limit. 145 | * 146 | * @param maxAge the max age of the authentication, in seconds. 147 | * @return this same builder instance. 148 | */ 149 | public Builder withAuthenticationMaxAge(Integer maxAge) { 150 | Validate.notNull(maxAge); 151 | this.authenticationMaxAge = maxAge; 152 | return this; 153 | } 154 | 155 | /** 156 | * Sets whether fallback cookies will be set for clients that do not support SameSite=None cookie attribute. 157 | * The SameSite Cookie attribute will only be set to "None" if the reponseType includes "id_token". 158 | * By default this is true. 159 | * @param useLegacySameSiteCookie whether fallback auth-based cookies should be set. 160 | * @return this same builder instance. 161 | */ 162 | public Builder withLegacySameSiteCookie(boolean useLegacySameSiteCookie) { 163 | this.useLegacySameSiteCookie = useLegacySameSiteCookie; 164 | return this; 165 | } 166 | 167 | /** 168 | * Sets the organization query string parameter value used to login to an organization. 169 | * 170 | * @param organization The ID or name of the organization to log the user in to. 171 | * @return the builder instance. 172 | */ 173 | public Builder withOrganization(String organization) { 174 | Validate.notNull(organization); 175 | this.organization = organization; 176 | return this; 177 | } 178 | 179 | /** 180 | * Sets the invitation query string parameter to join an organization. If using this, you must also specify the 181 | * organization using {@linkplain Builder#withOrganization(String)}. 182 | * 183 | * @param invitation The ID of the invitation to accept. This is available on the URL that is provided when accepting an invitation. 184 | * @return the builder instance. 185 | */ 186 | public Builder withInvitation(String invitation) { 187 | Validate.notNull(invitation); 188 | this.invitation = invitation; 189 | return this; 190 | } 191 | 192 | /** 193 | * Create a new {@link AuthenticationController} instance that will handle both Code Grant and Implicit Grant flows using either Code Exchange or Token Signature verification. 194 | * 195 | * @return a new instance of {@link AuthenticationController}. 196 | * @throws UnsupportedOperationException if the Implicit Grant is chosen and the environment doesn't support UTF-8 encoding. 197 | */ 198 | public AuthenticationController build() throws UnsupportedOperationException { 199 | AuthAPI apiClient = createAPIClient(domain, clientId, clientSecret, httpOptions); 200 | setupTelemetry(apiClient); 201 | 202 | final boolean expectedAlgorithmIsExplicitlySetAndAsymmetric = jwkProvider != null; 203 | final SignatureVerifier signatureVerifier; 204 | if (expectedAlgorithmIsExplicitlySetAndAsymmetric) { 205 | signatureVerifier = new AsymmetricSignatureVerifier(jwkProvider); 206 | } else if (responseType.contains(RESPONSE_TYPE_CODE)) { 207 | // Old behavior: To maintain backwards-compatibility when 208 | // no explicit algorithm is set by the user, we 209 | // must skip ID Token signature check. 210 | signatureVerifier = new AlgorithmNameVerifier(); 211 | } else { 212 | signatureVerifier = new SymmetricSignatureVerifier(clientSecret); 213 | } 214 | 215 | String issuer = getIssuer(domain); 216 | IdTokenVerifier.Options verifyOptions = createIdTokenVerificationOptions(issuer, clientId, signatureVerifier); 217 | verifyOptions.setClockSkew(clockSkew); 218 | verifyOptions.setMaxAge(authenticationMaxAge); 219 | verifyOptions.setOrganization(this.organization); 220 | 221 | RequestProcessor processor = new RequestProcessor.Builder(apiClient, responseType, verifyOptions) 222 | .withLegacySameSiteCookie(useLegacySameSiteCookie) 223 | .withOrganization(organization) 224 | .withInvitation(invitation) 225 | .withCookiePath(cookiePath) 226 | .build(); 227 | 228 | return new AuthenticationController(processor); 229 | } 230 | 231 | @VisibleForTesting 232 | IdTokenVerifier.Options createIdTokenVerificationOptions(String issuer, String audience, SignatureVerifier signatureVerifier) { 233 | return new IdTokenVerifier.Options(issuer, audience, signatureVerifier); 234 | } 235 | 236 | @VisibleForTesting 237 | AuthAPI createAPIClient(String domain, String clientId, String clientSecret, HttpOptions httpOptions) { 238 | if (httpOptions != null) { 239 | return new AuthAPI(domain, clientId, clientSecret, httpOptions); 240 | } 241 | return new AuthAPI(domain, clientId, clientSecret); 242 | } 243 | 244 | @VisibleForTesting 245 | void setupTelemetry(AuthAPI client) { 246 | Telemetry telemetry = new Telemetry("auth0-java-mvc-common", obtainPackageVersion()); 247 | client.setTelemetry(telemetry); 248 | } 249 | 250 | @VisibleForTesting 251 | String obtainPackageVersion() { 252 | //Value if taken from jar's manifest file. 253 | //Call will return null on dev environment (outside of a jar) 254 | return getClass().getPackage().getImplementationVersion(); 255 | } 256 | 257 | private String getIssuer(String domain) { 258 | if (!domain.startsWith("http://") && !domain.startsWith("https://")) { 259 | domain = "https://" + domain; 260 | } 261 | if (!domain.endsWith("/")) { 262 | domain = domain + "/"; 263 | } 264 | return domain; 265 | } 266 | } 267 | 268 | /** 269 | * Whether to enable or not the HTTP Logger for every Request and Response. 270 | * Enabling this can expose sensitive information. 271 | * 272 | * @param enabled whether to enable the HTTP logger or not. 273 | */ 274 | public void setLoggingEnabled(boolean enabled) { 275 | requestProcessor.getClient().setLoggingEnabled(enabled); 276 | } 277 | 278 | /** 279 | * Disable sending the Telemetry header on every request to the Auth0 API 280 | */ 281 | public void doNotSendTelemetry() { 282 | requestProcessor.getClient().doNotSendTelemetry(); 283 | } 284 | 285 | /** 286 | * Process a request to obtain a set of {@link Tokens} that represent successful authentication or authorization. 287 | * 288 | * This method should be called when processing the callback request to your application. It will validate 289 | * authentication-related request parameters, handle performing a Code Exchange request if using 290 | * the "code" response type, and verify the integrity of the ID token (if present). 291 | * 292 | *

Important: When using this API, you must also use {@link AuthenticationController#buildAuthorizeUrl(HttpServletRequest, HttpServletResponse, String)} 293 | * when building the {@link AuthorizeUrl} that the user will be redirected to to login. Failure to do so may result 294 | * in a broken login experience for the user.

295 | * 296 | * @param request the received request to process. 297 | * @param response the received response to process. 298 | * @return the Tokens obtained after the user authentication. 299 | * @throws InvalidRequestException if the error is result of making an invalid authentication request. 300 | * @throws IdentityVerificationException if an error occurred while verifying the request tokens. 301 | */ 302 | public Tokens handle(HttpServletRequest request, HttpServletResponse response) throws IdentityVerificationException { 303 | Validate.notNull(request, "request must not be null"); 304 | Validate.notNull(response, "response must not be null"); 305 | 306 | return requestProcessor.process(request, response); 307 | } 308 | 309 | /** 310 | * Process a request to obtain a set of {@link Tokens} that represent successful authentication or authorization. 311 | * 312 | * This method should be called when processing the callback request to your application. It will validate 313 | * authentication-related request parameters, handle performing a Code Exchange request if using 314 | * the "code" response type, and verify the integrity of the ID token (if present). 315 | * 316 | *

Important: When using this API, you must also use the {@link AuthenticationController#buildAuthorizeUrl(HttpServletRequest, String)} 317 | * when building the {@link AuthorizeUrl} that the user will be redirected to to login. Failure to do so may result 318 | * in a broken login experience for the user.

319 | * 320 | * @deprecated This method uses the {@link javax.servlet.http.HttpSession} for auth-based data, and is incompatible 321 | * with clients that are using the "id_token" or "token" responseType with browsers that enforce SameSite cookie 322 | * restrictions. This method will be removed in version 2.0.0. Use 323 | * {@link AuthenticationController#handle(HttpServletRequest, HttpServletResponse)} instead. 324 | * 325 | * @param request the received request to process. 326 | * @return the Tokens obtained after the user authentication. 327 | * @throws InvalidRequestException if the error is result of making an invalid authentication request. 328 | * @throws IdentityVerificationException if an error occurred while verifying the request tokens. 329 | */ 330 | @Deprecated 331 | public Tokens handle(HttpServletRequest request) throws IdentityVerificationException { 332 | Validate.notNull(request, "request must not be null"); 333 | 334 | return requestProcessor.process(request, null); 335 | } 336 | 337 | /** 338 | * Pre builds an Auth0 Authorize Url with the given redirect URI using a random state and a random nonce if applicable. 339 | * 340 | *

Important: When using this API, you must also obtain the tokens using the 341 | * {@link AuthenticationController#handle(HttpServletRequest)} method. Failure to do so may result in a broken login 342 | * experience for users.

343 | * 344 | * @deprecated This method stores data in the {@link javax.servlet.http.HttpSession}, and is incompatible with clients 345 | * that are using the "id_token" or "token" responseType with browsers that enforce SameSite cookie restrictions. 346 | * This method will be removed in version 2.0.0. Use 347 | * {@link AuthenticationController#buildAuthorizeUrl(HttpServletRequest, HttpServletResponse, String)} instead. 348 | * 349 | * @param request the caller request. Used to keep the session context. 350 | * @param redirectUri the url to call back with the authentication result. 351 | * @return the authorize url builder to continue any further parameter customization. 352 | */ 353 | @Deprecated 354 | public AuthorizeUrl buildAuthorizeUrl(HttpServletRequest request, String redirectUri) { 355 | Validate.notNull(request, "request must not be null"); 356 | Validate.notNull(redirectUri, "redirectUri must not be null"); 357 | 358 | String state = StorageUtils.secureRandomString(); 359 | String nonce = StorageUtils.secureRandomString(); 360 | 361 | return requestProcessor.buildAuthorizeUrl(request, null, redirectUri, state, nonce); 362 | } 363 | 364 | /** 365 | * Pre builds an Auth0 Authorize Url with the given redirect URI using a random state and a random nonce if applicable. 366 | * 367 | *

Important: When using this API, you must also obtain the tokens using the 368 | * {@link AuthenticationController#handle(HttpServletRequest, HttpServletResponse)} method. Failure to do so will result in a broken login 369 | * experience for users.

370 | * 371 | * @param request the HTTP request 372 | * @param response the HTTP response. Used to store auth-based cookies. 373 | * @param redirectUri the url to call back with the authentication result. 374 | * @return the authorize url builder to continue any further parameter customization. 375 | */ 376 | public AuthorizeUrl buildAuthorizeUrl(HttpServletRequest request, HttpServletResponse response, String redirectUri) { 377 | Validate.notNull(request, "request must not be null"); 378 | Validate.notNull(response, "response must not be null"); 379 | Validate.notNull(redirectUri, "redirectUri must not be null"); 380 | 381 | String state = StorageUtils.secureRandomString(); 382 | String nonce = StorageUtils.secureRandomString(); 383 | 384 | return requestProcessor.buildAuthorizeUrl(request, response, redirectUri, state, nonce); 385 | } 386 | 387 | } 388 | -------------------------------------------------------------------------------- /src/main/java/com/auth0/AuthorizeUrl.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | import com.auth0.client.auth.AuthAPI; 4 | import com.auth0.client.auth.AuthorizeUrlBuilder; 5 | import com.auth0.exception.Auth0Exception; 6 | import com.auth0.json.auth.PushedAuthorizationResponse; 7 | 8 | import javax.servlet.http.HttpServletRequest; 9 | import javax.servlet.http.HttpServletResponse; 10 | import java.util.*; 11 | 12 | import static com.auth0.IdentityVerificationException.API_ERROR; 13 | 14 | /** 15 | * Class to create and customize an Auth0 Authorize URL. 16 | * It's not reusable. 17 | */ 18 | @SuppressWarnings({"UnusedReturnValue", "WeakerAccess", "unused", "SameParameterValue"}) 19 | public class AuthorizeUrl { 20 | 21 | private static final String SCOPE_OPENID = "openid"; 22 | 23 | private HttpServletResponse response; 24 | private HttpServletRequest request; 25 | private final String responseType; 26 | private boolean useLegacySameSiteCookie = true; 27 | private boolean setSecureCookie = false; 28 | private String nonce; 29 | private String state; 30 | private final AuthAPI authAPI; 31 | private String cookiePath; 32 | 33 | private boolean used; 34 | private Map params; 35 | private final String redirectUri; 36 | 37 | /** 38 | * Creates a new instance that can be used to build an Auth0 Authorization URL. 39 | * 40 | * Using this constructor with a non-null {@link HttpServletResponse} will store the state and nonce as 41 | * cookies when the {@link AuthorizeUrl#build()} method is called, with the appropriate SameSite attribute depending 42 | * on the responseType. State and nonce will also be stored in the {@link javax.servlet.http.HttpSession} as a fallback, 43 | * but this behavior will be removed in a future release, and only cookies will be used. 44 | * 45 | * @param client the Auth0 Authentication API client 46 | * @parem request the HTTP request. Used to store state and nonce as a fallback if cookies not set. 47 | * @param response the response where the state and nonce will be stored as cookies 48 | * @param redirectUri the url to redirect to after authentication 49 | * @param responseType the response type to use 50 | */ 51 | AuthorizeUrl(AuthAPI client, HttpServletRequest request, HttpServletResponse response, String redirectUri, String responseType) { 52 | this.request = request; 53 | this.response = response; 54 | this.responseType = responseType; 55 | this.authAPI = client; 56 | this.redirectUri = redirectUri; 57 | this.params = new HashMap<>(); 58 | this.params.put("scope", SCOPE_OPENID); 59 | } 60 | 61 | /** 62 | * Sets the organization query string parameter value used to login to an organization. 63 | * 64 | * @param organization The ID of the organization to log the user in to. 65 | * @return the builder instance. 66 | */ 67 | public AuthorizeUrl withOrganization(String organization) { 68 | params.put("organization", organization); 69 | return this; 70 | } 71 | 72 | /** 73 | * Sets the invitation query string parameter to join an organization. If using this, you must also specify the 74 | * organization using {@linkplain AuthorizeUrl#withOrganization(String)}. 75 | * 76 | * @param invitation The ID of the invitation to accept. This is available on the URL that is provided when accepting an invitation. 77 | * @return the builder instance. 78 | */ 79 | public AuthorizeUrl withInvitation(String invitation) { 80 | params.put("invitation", invitation); 81 | return this; 82 | } 83 | 84 | /** 85 | * Sets the connection value. 86 | * 87 | * @param connection connection to set 88 | * @return the builder instance 89 | */ 90 | public AuthorizeUrl withConnection(String connection) { 91 | params.put("connection", connection); 92 | return this; 93 | } 94 | 95 | /** 96 | * Sets whether cookies used during the authentication flow have the {@code Secure} attribute set or not. 97 | * By default, cookies will be set with the Secure attribute if the responseType includes {@code id_token} and thus requires 98 | * the {@code SameSite=None} cookie attribute set. Setting this to false will not override this behavior, 99 | * as clients will reject cookies with {@code SameSite=None} unless the {@code Secure} attribute is set. 100 | * 101 | * While not guaranteed by all clients, generally a cookie with the {@code Secure} attribute will be rejected unless 102 | * served over HTTPS. 103 | * 104 | * @param secureCookie whether to always set the Secure attribute on all cookies. 105 | * @return the builder instance. 106 | */ 107 | public AuthorizeUrl withSecureCookie(boolean secureCookie) { 108 | this.setSecureCookie = secureCookie; 109 | return this; 110 | } 111 | 112 | /** 113 | * Sets whether a fallback cookie should be used for clients that do not support "SameSite=None". 114 | * Only applicable when this instance is created with {@link AuthorizeUrl#AuthorizeUrl(AuthAPI, HttpServletRequest, HttpServletResponse, String, String)}. 115 | * 116 | * @param useLegacySameSiteCookie whether or not to set fallback auth cookies for clients that do not support "SameSite=None" 117 | * @return the builder instance 118 | */ 119 | AuthorizeUrl withLegacySameSiteCookie(boolean useLegacySameSiteCookie) { 120 | this.useLegacySameSiteCookie = useLegacySameSiteCookie; 121 | return this; 122 | } 123 | 124 | /** 125 | * Sets the audience value. 126 | * 127 | * @param audience audience to set 128 | * @return the builder instance 129 | */ 130 | public AuthorizeUrl withAudience(String audience) { 131 | params.put("audience", audience); 132 | return this; 133 | } 134 | 135 | /** 136 | * Sets the value of the Path cookie attribute 137 | * @param cookiePath the cookie path to set 138 | * @return 139 | */ 140 | AuthorizeUrl withCookiePath(String cookiePath) { 141 | this.cookiePath = cookiePath; 142 | return this; 143 | } 144 | 145 | /** 146 | * Sets the state value. 147 | * 148 | * @param state state to set 149 | * @return the builder instance 150 | */ 151 | public AuthorizeUrl withState(String state) { 152 | this.state = state; 153 | params.put("state", state); 154 | return this; 155 | } 156 | 157 | /** 158 | * Sets the nonce value. 159 | * 160 | * @param nonce nonce to set 161 | * @return the builder instance 162 | */ 163 | public AuthorizeUrl withNonce(String nonce) { 164 | this.nonce = nonce; 165 | params.put("nonce", nonce); 166 | return this; 167 | } 168 | 169 | /** 170 | * Sets the scope value. 171 | * 172 | * @param scope scope to set 173 | * @return the builder instance 174 | */ 175 | public AuthorizeUrl withScope(String scope) { 176 | params.put("scope", scope); 177 | return this; 178 | } 179 | 180 | /** 181 | * Sets an additional parameter. 182 | * 183 | * @param name name of the parameter 184 | * @param value value of the parameter to set 185 | * @return the builder instance 186 | */ 187 | public AuthorizeUrl withParameter(String name, String value) { 188 | if ("state".equals(name) || "nonce".equals(name)) { 189 | throw new IllegalArgumentException("Please, use the dedicated methods for setting the 'nonce' and 'state' parameters."); 190 | } 191 | if ("response_type".equals(name)) { 192 | throw new IllegalArgumentException("Response type cannot be changed once set."); 193 | } 194 | if ("redirect_uri".equals(name)) { 195 | throw new IllegalArgumentException("Redirect URI cannot be changed once set."); 196 | } 197 | params.put(name, value); 198 | return this; 199 | } 200 | 201 | /** 202 | * Creates a string representation of the URL with the configured parameters. 203 | * It cannot be called more than once. 204 | * 205 | * @return the string URL 206 | * @throws IllegalStateException if it's called more than once 207 | */ 208 | public String build() throws IllegalStateException { 209 | storeTransient(); 210 | AuthorizeUrlBuilder builder = authAPI.authorizeUrl(redirectUri).withResponseType(responseType); 211 | params.forEach(builder::withParameter); 212 | return builder.build(); 213 | } 214 | 215 | /** 216 | * Executes a Pushed Authorization Request (PAR) and uses the {@code request_uri} to 217 | * construct the authorize URL. 218 | * 219 | * @return the authorize URL as a string. 220 | * @throws InvalidRequestException if there is an error when making the request. 221 | * @see RFC 9126 222 | */ 223 | public String fromPushedAuthorizationRequest() throws InvalidRequestException { 224 | storeTransient(); 225 | 226 | try { 227 | PushedAuthorizationResponse pushedAuthResponse = authAPI.pushedAuthorizationRequest(redirectUri, responseType, params).execute(); 228 | String requestUri = pushedAuthResponse.getRequestURI(); 229 | if (requestUri == null || requestUri.isEmpty()) { 230 | throw new InvalidRequestException(API_ERROR, "The PAR request returned a missing or empty request_uri value"); 231 | } 232 | if (pushedAuthResponse.getExpiresIn() == null) { 233 | throw new InvalidRequestException(API_ERROR, "The PAR request returned a missing expires_in value"); 234 | } 235 | return authAPI.authorizeUrlWithPAR(pushedAuthResponse.getRequestURI()); 236 | } catch (Auth0Exception e) { 237 | throw new InvalidRequestException(API_ERROR, e.getMessage(), e); 238 | } 239 | } 240 | 241 | private void storeTransient() { 242 | if (used) { 243 | throw new IllegalStateException("The AuthorizeUrl instance must not be reused."); 244 | } 245 | 246 | if (response != null) { 247 | SameSite sameSiteValue = containsFormPost() ? SameSite.NONE : SameSite.LAX; 248 | 249 | TransientCookieStore.storeState(response, state, sameSiteValue, useLegacySameSiteCookie, setSecureCookie, cookiePath); 250 | TransientCookieStore.storeNonce(response, nonce, sameSiteValue, useLegacySameSiteCookie, setSecureCookie, cookiePath); 251 | } 252 | 253 | // Also store in Session just in case developer uses deprecated 254 | // AuthenticationController.handle(HttpServletRequest) API 255 | RandomStorage.setSessionState(request, state); 256 | RandomStorage.setSessionNonce(request, nonce); 257 | 258 | used = true; 259 | } 260 | 261 | private boolean containsFormPost() { 262 | String[] splitResponseTypes = responseType.trim().split("\\s+"); 263 | List responseTypes = Collections.unmodifiableList(Arrays.asList(splitResponseTypes)); 264 | return RequestProcessor.requiresFormPostResponseMode(responseTypes); 265 | } 266 | 267 | } 268 | -------------------------------------------------------------------------------- /src/main/java/com/auth0/IdTokenVerifier.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | import com.auth0.jwt.interfaces.DecodedJWT; 4 | import org.apache.commons.lang3.Validate; 5 | 6 | import java.util.Calendar; 7 | import java.util.Date; 8 | import java.util.List; 9 | 10 | /** 11 | * Token verification utility class. 12 | * Supported signing algorithms: HS256 and RS256 13 | */ 14 | class IdTokenVerifier { 15 | 16 | private static final Integer DEFAULT_CLOCK_SKEW = 60; //1 min = 60 sec 17 | 18 | private static final String NONCE_CLAIM = "nonce"; 19 | private static final String AZP_CLAIM = "azp"; 20 | private static final String AUTH_TIME_CLAIM = "auth_time"; 21 | 22 | /** 23 | * Verifies a provided ID Token follows the OIDC specification. 24 | * @see Open ID Connect Specification 25 | * 26 | * @param token the ID Token to verify. 27 | * @param verifyOptions the verification options, like audience, issuer, algorithm. 28 | * @throws TokenValidationException If the ID Token is null, its signing algorithm not supported, its signature invalid or one of its claim invalid. 29 | */ 30 | void verify(String token, Options verifyOptions) throws TokenValidationException { 31 | Validate.notNull(verifyOptions); 32 | 33 | if (isEmpty(token)) { 34 | throw new TokenValidationException("ID token is required but missing"); 35 | } 36 | 37 | DecodedJWT decoded = verifyOptions.verifier.verifySignature(token); 38 | 39 | if (isEmpty(decoded.getIssuer())) { 40 | throw new TokenValidationException("Issuer (iss) claim must be a string present in the ID token"); 41 | } 42 | if (!decoded.getIssuer().equals(verifyOptions.issuer)) { 43 | throw new TokenValidationException(String.format("Issuer (iss) claim mismatch in the ID token, expected \"%s\", found \"%s\"", verifyOptions.issuer, decoded.getIssuer())); 44 | } 45 | 46 | if (isEmpty(decoded.getSubject())) { 47 | throw new TokenValidationException("Subject (sub) claim must be a string present in the ID token"); 48 | } 49 | 50 | final List audience = decoded.getAudience(); 51 | if (audience == null) { 52 | throw new TokenValidationException("Audience (aud) claim must be a string or array of strings present in the ID token"); 53 | } 54 | if (!audience.contains(verifyOptions.audience)) { 55 | throw new TokenValidationException(String.format("Audience (aud) claim mismatch in the ID token; expected \"%s\" but found \"%s\"", verifyOptions.audience, decoded.getAudience())); 56 | } 57 | 58 | // validate org if set 59 | if (verifyOptions.organization != null) { 60 | String org = verifyOptions.organization.trim(); 61 | if (org.startsWith("org_")) { 62 | // org ID 63 | String orgIdClaim = decoded.getClaim("org_id").asString(); 64 | if (isEmpty(orgIdClaim)) { 65 | throw new TokenValidationException("Organization Id (org_id) claim must be a string present in the ID token"); 66 | } 67 | if (!org.equals(orgIdClaim)) { 68 | throw new TokenValidationException(String.format("Organization (org_id) claim mismatch in the ID token; expected \"%s\" but found \"%s\"", verifyOptions.organization, orgIdClaim)); 69 | } 70 | } 71 | else { 72 | // org name 73 | String orgNameClaim = decoded.getClaim("org_name").asString(); 74 | if (isEmpty(orgNameClaim)) { 75 | throw new TokenValidationException("Organization name (org_name) claim must be a string present in the ID token"); 76 | } 77 | if (!org.toLowerCase().equals(orgNameClaim)) { 78 | throw new TokenValidationException(String.format("Organization (org_name) claim mismatch in the ID token; expected \"%s\" but found \"%s\"", verifyOptions.organization, orgNameClaim)); 79 | } 80 | } 81 | } 82 | 83 | // TODO refactor to modern date/time APIs 84 | final Calendar cal = Calendar.getInstance(); 85 | final Date now = verifyOptions.clock != null ? verifyOptions.clock : cal.getTime(); 86 | final int clockSkew = verifyOptions.clockSkew != null ? verifyOptions.clockSkew : DEFAULT_CLOCK_SKEW; 87 | 88 | if (decoded.getExpiresAt() == null) { 89 | throw new TokenValidationException("Expiration Time (exp) claim must be a number present in the ID token"); 90 | } 91 | 92 | cal.setTime(decoded.getExpiresAt()); 93 | cal.add(Calendar.SECOND, clockSkew); 94 | Date expDate = cal.getTime(); 95 | 96 | if (now.after(expDate)) { 97 | throw new TokenValidationException(String.format("Expiration Time (exp) claim error in the ID token; current time (%d) is after expiration time (%d)", now.getTime() / 1000, expDate.getTime() / 1000)); 98 | } 99 | 100 | if (decoded.getIssuedAt() == null) { 101 | throw new TokenValidationException("Issued At (iat) claim must be a number present in the ID token"); 102 | } 103 | 104 | cal.setTime(decoded.getIssuedAt()); 105 | cal.add(Calendar.SECOND, -1 * clockSkew); 106 | 107 | if (verifyOptions.nonce != null) { 108 | String nonceClaim = decoded.getClaim(NONCE_CLAIM).asString(); 109 | if (isEmpty(nonceClaim)) { 110 | throw new TokenValidationException("Nonce (nonce) claim must be a string present in the ID token"); 111 | } 112 | if (!verifyOptions.nonce.equals(nonceClaim)) { 113 | throw new TokenValidationException(String.format("Nonce (nonce) claim mismatch in the ID token; expected \"%s\", found \"%s\"", verifyOptions.nonce, nonceClaim)); 114 | } 115 | } 116 | 117 | if (audience.size() > 1) { 118 | String azpClaim = decoded.getClaim(AZP_CLAIM).asString(); 119 | if (isEmpty(azpClaim)) { 120 | throw new TokenValidationException("Authorized Party (azp) claim must be a string present in the ID token when Audience (aud) claim has multiple values"); 121 | } 122 | if (!verifyOptions.audience.equals(azpClaim)) { 123 | throw new TokenValidationException(String.format("Authorized Party (azp) claim mismatch in the ID token; expected \"%s\", found \"%s\"", verifyOptions.audience, azpClaim)); 124 | } 125 | } 126 | 127 | if (verifyOptions.maxAge != null) { 128 | Date authTime = decoded.getClaim(AUTH_TIME_CLAIM).asDate(); 129 | if (authTime == null) { 130 | throw new TokenValidationException("Authentication Time (auth_time) claim must be a number present in the ID token when Max Age (max_age) is specified"); 131 | } 132 | 133 | cal.setTime(authTime); 134 | cal.add(Calendar.SECOND, verifyOptions.maxAge); 135 | cal.add(Calendar.SECOND, clockSkew); 136 | Date authTimeDate = cal.getTime(); 137 | 138 | if (now.after(authTimeDate)) { 139 | throw new TokenValidationException(String.format("Authentication Time (auth_time) claim in the ID token indicates that too much time has passed since the last end-user authentication. Current time (%d) is after last auth at (%d)", now.getTime() / 1000, authTimeDate.getTime() / 1000)); 140 | } 141 | } 142 | } 143 | 144 | private boolean isEmpty(String value) { 145 | return value == null || value.isEmpty(); 146 | } 147 | 148 | static class Options { 149 | final String issuer; 150 | final String audience; 151 | final SignatureVerifier verifier; 152 | String nonce; 153 | private Integer maxAge; 154 | Integer clockSkew; 155 | Date clock; 156 | String organization; 157 | 158 | public Options(String issuer, String audience, SignatureVerifier verifier) { 159 | Validate.notNull(issuer); 160 | Validate.notNull(audience); 161 | Validate.notNull(verifier); 162 | this.issuer = issuer; 163 | this.audience = audience; 164 | this.verifier = verifier; 165 | } 166 | 167 | void setNonce(String nonce) { 168 | this.nonce = nonce; 169 | } 170 | 171 | void setMaxAge(Integer maxAge) { 172 | this.maxAge = maxAge; 173 | } 174 | 175 | void setClockSkew(Integer clockSkew) { 176 | this.clockSkew = clockSkew; 177 | } 178 | 179 | void setClock(Date now) { 180 | this.clock = now; 181 | } 182 | 183 | Integer getMaxAge() { 184 | return maxAge; 185 | } 186 | 187 | void setOrganization(String organization) { 188 | this.organization = organization; 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/main/java/com/auth0/IdentityVerificationException.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | @SuppressWarnings("WeakerAccess") 4 | public class IdentityVerificationException extends Exception { 5 | 6 | static final String API_ERROR = "a0.api_error"; 7 | static final String JWT_MISSING_PUBLIC_KEY_ERROR = "a0.missing_jwt_public_key_error"; 8 | static final String JWT_VERIFICATION_ERROR = "a0.invalid_jwt_error"; 9 | private final String code; 10 | 11 | IdentityVerificationException(String code, String message, Throwable cause) { 12 | super(message, cause); 13 | this.code = code; 14 | } 15 | 16 | /** 17 | * Getter for the code of the error. 18 | * 19 | * @return the error code. 20 | */ 21 | public String getCode() { 22 | return code; 23 | } 24 | 25 | public boolean isAPIError() { 26 | return API_ERROR.equals(code); 27 | } 28 | 29 | public boolean isJWTError() { 30 | return JWT_MISSING_PUBLIC_KEY_ERROR.equals(code) || JWT_VERIFICATION_ERROR.equals(code); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/auth0/InvalidRequestException.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | /** 4 | * Represents an error occurred while executing a request against the Auth0 Authentication API 5 | */ 6 | @SuppressWarnings("WeakerAccess") 7 | public class InvalidRequestException extends IdentityVerificationException { 8 | static final String INVALID_STATE_ERROR = "a0.invalid_state"; 9 | static final String MISSING_ID_TOKEN = "a0.missing_id_token"; 10 | static final String MISSING_ACCESS_TOKEN = "a0.missing_access_token"; 11 | static final String DEFAULT_DESCRIPTION = "The request contains an error"; 12 | 13 | InvalidRequestException(String code, String description) { 14 | this(code, description, null); 15 | } 16 | 17 | InvalidRequestException(String code, String description, Throwable cause) { 18 | super(code, description != null ? description : DEFAULT_DESCRIPTION, cause); 19 | } 20 | 21 | /** 22 | * Getter for the description of the error. 23 | * 24 | * @return the error description if available, null otherwise. 25 | * @deprecated use {@link #getMessage()} 26 | */ 27 | @Deprecated 28 | public String getDescription() { 29 | return getMessage(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/auth0/RandomStorage.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import javax.servlet.http.HttpSession; 5 | 6 | class RandomStorage extends SessionUtils { 7 | 8 | /** 9 | * Check's if the request {@link HttpSession} saved state is equal to the given state. 10 | * After the check, the value will be removed from the session. 11 | * 12 | * @param req the request 13 | * @param state the state value to compare against. 14 | * @return whether the state matches the expected one or not. 15 | */ 16 | static boolean checkSessionState(HttpServletRequest req, String state) { 17 | String currentState = (String) remove(req, StorageUtils.STATE_KEY); 18 | return (currentState == null && state == null) || currentState != null && currentState.equals(state); 19 | } 20 | 21 | /** 22 | * Saves the given state in the request {@link HttpSession}. 23 | * If a state is already bound to the session, the value is replaced. 24 | * 25 | * @param req the request. 26 | * @param state the state value to set. 27 | */ 28 | static void setSessionState(HttpServletRequest req, String state) { 29 | set(req, StorageUtils.STATE_KEY, state); 30 | } 31 | 32 | /** 33 | * Saves the given nonce in the request {@link HttpSession}. 34 | * If a nonce is already bound to the session, the value is replaced. 35 | * 36 | * @param req the request. 37 | * @param nonce the nonce value to set. 38 | */ 39 | static void setSessionNonce(HttpServletRequest req, String nonce) { 40 | set(req, StorageUtils.NONCE_KEY, nonce); 41 | } 42 | 43 | /** 44 | * Removes the nonce present in the request {@link HttpSession} and then returns it. 45 | * 46 | * @param req the HTTP Servlet request. 47 | * @return the nonce value or null if it was not set. 48 | */ 49 | static String removeSessionNonce(HttpServletRequest req) { 50 | return (String) remove(req, StorageUtils.NONCE_KEY); 51 | } 52 | } -------------------------------------------------------------------------------- /src/main/java/com/auth0/RequestProcessor.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | import com.auth0.client.auth.AuthAPI; 4 | import com.auth0.exception.Auth0Exception; 5 | import com.auth0.json.auth.TokenHolder; 6 | import org.apache.commons.lang3.Validate; 7 | 8 | import javax.servlet.http.HttpServletRequest; 9 | import javax.servlet.http.HttpServletResponse; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | 13 | import static com.auth0.InvalidRequestException.*; 14 | 15 | /** 16 | * Main class to handle the Authorize Redirect request. 17 | * It will try to parse the parameters looking for tokens or an authorization code to perform a Code Exchange against the Auth0 servers. 18 | */ 19 | class RequestProcessor { 20 | 21 | private static final String KEY_STATE = "state"; 22 | private static final String KEY_ERROR = "error"; 23 | private static final String KEY_ERROR_DESCRIPTION = "error_description"; 24 | private static final String KEY_EXPIRES_IN = "expires_in"; 25 | private static final String KEY_ACCESS_TOKEN = "access_token"; 26 | private static final String KEY_ID_TOKEN = "id_token"; 27 | private static final String KEY_TOKEN_TYPE = "token_type"; 28 | private static final String KEY_CODE = "code"; 29 | private static final String KEY_TOKEN = "token"; 30 | private static final String KEY_RESPONSE_MODE = "response_mode"; 31 | private static final String KEY_FORM_POST = "form_post"; 32 | private static final String KEY_MAX_AGE = "max_age"; 33 | 34 | // Visible for testing 35 | final IdTokenVerifier.Options verifyOptions; 36 | final boolean useLegacySameSiteCookie; 37 | 38 | private final String responseType; 39 | private final AuthAPI client; 40 | private final IdTokenVerifier tokenVerifier; 41 | private final String organization; 42 | private final String invitation; 43 | private final String cookiePath; 44 | 45 | 46 | static class Builder { 47 | private final AuthAPI client; 48 | private final String responseType; 49 | private final IdTokenVerifier.Options verifyOptions; 50 | private boolean useLegacySameSiteCookie = true; 51 | private IdTokenVerifier tokenVerifier; 52 | private String organization; 53 | private String invitation; 54 | private String cookiePath; 55 | 56 | Builder(AuthAPI client, String responseType, IdTokenVerifier.Options verifyOptions) { 57 | Validate.notNull(client); 58 | Validate.notNull(responseType); 59 | Validate.notNull(verifyOptions); 60 | this.client = client; 61 | this.responseType = responseType; 62 | this.verifyOptions = verifyOptions; 63 | } 64 | 65 | Builder withCookiePath(String cookiePath) { 66 | this.cookiePath = cookiePath; 67 | return this; 68 | } 69 | 70 | Builder withLegacySameSiteCookie(boolean useLegacySameSiteCookie) { 71 | this.useLegacySameSiteCookie = useLegacySameSiteCookie; 72 | return this; 73 | } 74 | 75 | Builder withIdTokenVerifier(IdTokenVerifier verifier) { 76 | this.tokenVerifier = verifier; 77 | return this; 78 | } 79 | 80 | Builder withOrganization(String organization) { 81 | this.organization = organization; 82 | return this; 83 | } 84 | 85 | Builder withInvitation(String invitation) { 86 | this.invitation = invitation; 87 | return this; 88 | } 89 | 90 | RequestProcessor build() { 91 | return new RequestProcessor(client, responseType, verifyOptions, 92 | this.tokenVerifier == null ? new IdTokenVerifier() : this.tokenVerifier, 93 | useLegacySameSiteCookie, organization, invitation, cookiePath); 94 | } 95 | } 96 | 97 | private RequestProcessor(AuthAPI client, String responseType, IdTokenVerifier.Options verifyOptions, IdTokenVerifier tokenVerifier, boolean useLegacySameSiteCookie, String organization, String invitation, String cookiePath) { 98 | Validate.notNull(client); 99 | Validate.notNull(responseType); 100 | Validate.notNull(verifyOptions); 101 | this.client = client; 102 | this.responseType = responseType; 103 | this.verifyOptions = verifyOptions; 104 | this.tokenVerifier = tokenVerifier; 105 | this.useLegacySameSiteCookie = useLegacySameSiteCookie; 106 | this.organization = organization; 107 | this.invitation = invitation; 108 | this.cookiePath = cookiePath; 109 | } 110 | 111 | /** 112 | * Getter for the AuthAPI client instance. 113 | * Used to customize options such as Telemetry and Logging. 114 | * 115 | * @return the AuthAPI client. 116 | */ 117 | AuthAPI getClient() { 118 | return client; 119 | } 120 | 121 | /** 122 | * Pre builds an Auth0 Authorize Url with the given redirect URI, state and nonce parameters. 123 | * 124 | * @param request the request, used to store state and nonce in the Session 125 | * @param response the response, used to set state and nonce as cookies. If null, session will be used instead. 126 | * @param redirectUri the url to call with the authentication result. 127 | * @param state a valid state value. 128 | * @param nonce the nonce value that will be used if the response type contains 'id_token'. Can be null. 129 | * @return the authorize url builder to continue any further parameter customization. 130 | */ 131 | AuthorizeUrl buildAuthorizeUrl(HttpServletRequest request, HttpServletResponse response, String redirectUri, 132 | String state, String nonce) { 133 | 134 | AuthorizeUrl creator = new AuthorizeUrl(client, request, response, redirectUri, responseType) 135 | .withState(state); 136 | 137 | if (this.organization != null) { 138 | creator.withOrganization(organization); 139 | } 140 | if (this.invitation != null) { 141 | creator.withInvitation(invitation); 142 | } 143 | if (this.cookiePath != null) { 144 | creator.withCookiePath(this.cookiePath); 145 | } 146 | 147 | // null response means state and nonce will be stored in session, so legacy cookie flag does not apply 148 | if (response != null) { 149 | creator.withLegacySameSiteCookie(useLegacySameSiteCookie); 150 | } 151 | 152 | 153 | return getAuthorizeUrl(nonce, creator); 154 | } 155 | 156 | /** 157 | * Entrypoint for HTTP request 158 | *

159 | * 1). Responsible for validating the request. 160 | * 2). Exchanging the authorization code received with this HTTP request for Auth0 tokens. 161 | * 3). Validating the ID Token. 162 | * 4). Clearing the stored state, nonce and max_age values. 163 | * 5). Handling success and any failure outcomes. 164 | * 165 | * @throws IdentityVerificationException if an error occurred while processing the request 166 | */ 167 | Tokens process(HttpServletRequest request, HttpServletResponse response) throws IdentityVerificationException { 168 | assertNoError(request); 169 | assertValidState(request, response); 170 | 171 | Tokens frontChannelTokens = getFrontChannelTokens(request); 172 | List responseTypeList = getResponseType(); 173 | 174 | if (responseTypeList.contains(KEY_ID_TOKEN) && frontChannelTokens.getIdToken() == null) { 175 | throw new InvalidRequestException(MISSING_ID_TOKEN, "ID Token is missing from the response."); 176 | } 177 | if (responseTypeList.contains(KEY_TOKEN) && frontChannelTokens.getAccessToken() == null) { 178 | throw new InvalidRequestException(MISSING_ACCESS_TOKEN, "Access Token is missing from the response."); 179 | } 180 | 181 | String nonce; 182 | if (response != null) { 183 | // Nonce dynamically set and changes on every request. 184 | nonce = TransientCookieStore.getNonce(request, response); 185 | 186 | // Just in case the developer created the authorizeUrl that stores state/nonce in the session 187 | if (nonce == null) { 188 | nonce = RandomStorage.removeSessionNonce(request); 189 | } 190 | } else { 191 | nonce = RandomStorage.removeSessionNonce(request); 192 | } 193 | 194 | verifyOptions.setNonce(nonce); 195 | 196 | return getVerifiedTokens(request, frontChannelTokens, responseTypeList); 197 | } 198 | 199 | static boolean requiresFormPostResponseMode(List responseType) { 200 | return responseType != null && 201 | (responseType.contains(KEY_TOKEN) || responseType.contains(KEY_ID_TOKEN)); 202 | } 203 | 204 | /** 205 | * Obtains code request tokens (if using Code flow) and validates the ID token. 206 | * @param request the HTTP request 207 | * @param frontChannelTokens the tokens obtained from the front channel 208 | * @param responseTypeList the reponse types 209 | * @return a Tokens object that wraps the values obtained from the front-channel and/or the code request response. 210 | * @throws IdentityVerificationException 211 | */ 212 | private Tokens getVerifiedTokens(HttpServletRequest request, Tokens frontChannelTokens, List responseTypeList) 213 | throws IdentityVerificationException { 214 | 215 | String authorizationCode = request.getParameter(KEY_CODE); 216 | Tokens codeExchangeTokens = null; 217 | 218 | try { 219 | if (responseTypeList.contains(KEY_ID_TOKEN)) { 220 | // Implicit/Hybrid flow: must verify front-channel ID Token first 221 | tokenVerifier.verify(frontChannelTokens.getIdToken(), verifyOptions); 222 | } 223 | if (responseTypeList.contains(KEY_CODE)) { 224 | // Code/Hybrid flow 225 | String redirectUri = request.getRequestURL().toString(); 226 | codeExchangeTokens = exchangeCodeForTokens(authorizationCode, redirectUri); 227 | if (!responseTypeList.contains(KEY_ID_TOKEN)) { 228 | // If we already verified the front-channel token, don't verify it again. 229 | String idTokenFromCodeExchange = codeExchangeTokens.getIdToken(); 230 | if (idTokenFromCodeExchange != null) { 231 | tokenVerifier.verify(idTokenFromCodeExchange, verifyOptions); 232 | } 233 | } 234 | } 235 | } catch (TokenValidationException e) { 236 | throw new IdentityVerificationException(JWT_VERIFICATION_ERROR, "An error occurred while trying to verify the ID Token.", e); 237 | } catch (Auth0Exception e) { 238 | throw new IdentityVerificationException(API_ERROR, "An error occurred while exchanging the authorization code.", e); 239 | } 240 | // Keep the front-channel ID Token and the code-exchange Access Token. 241 | return mergeTokens(frontChannelTokens, codeExchangeTokens); 242 | } 243 | 244 | List getResponseType() { 245 | return Arrays.asList(responseType.split(" ")); 246 | } 247 | 248 | private AuthorizeUrl getAuthorizeUrl(String nonce, AuthorizeUrl creator) { 249 | List responseTypeList = getResponseType(); 250 | if (responseTypeList.contains(KEY_ID_TOKEN) && nonce != null) { 251 | creator.withNonce(nonce); 252 | } 253 | if (requiresFormPostResponseMode(responseTypeList)) { 254 | creator.withParameter(KEY_RESPONSE_MODE, KEY_FORM_POST); 255 | } 256 | if (verifyOptions.getMaxAge() != null) { 257 | creator.withParameter(KEY_MAX_AGE, verifyOptions.getMaxAge().toString()); 258 | } 259 | return creator; 260 | } 261 | 262 | /** 263 | * Extract the tokens from the request parameters, present when using the Implicit or Hybrid Grant. 264 | * 265 | * @param request the request 266 | * @return a new instance of Tokens wrapping the values present in the request parameters. 267 | */ 268 | private Tokens getFrontChannelTokens(HttpServletRequest request) { 269 | Long expiresIn = request.getParameter(KEY_EXPIRES_IN) == null ? null : Long.parseLong(request.getParameter(KEY_EXPIRES_IN)); 270 | return new Tokens(request.getParameter(KEY_ACCESS_TOKEN), request.getParameter(KEY_ID_TOKEN), null, request.getParameter(KEY_TOKEN_TYPE), expiresIn); 271 | } 272 | 273 | /** 274 | * Checks for the presence of an error in the request parameters 275 | * 276 | * @param request the request 277 | * @throws InvalidRequestException if the request contains an error 278 | */ 279 | private void assertNoError(HttpServletRequest request) throws InvalidRequestException { 280 | String error = request.getParameter(KEY_ERROR); 281 | if (error != null) { 282 | String errorDescription = request.getParameter(KEY_ERROR_DESCRIPTION); 283 | throw new InvalidRequestException(error, errorDescription); 284 | } 285 | } 286 | 287 | /** 288 | * Checks whether the state received in the request parameters is the same as the one in the state cookie or session 289 | * for this request. 290 | * 291 | * @param request the request 292 | * @throws InvalidRequestException if the request contains a different state from the expected one 293 | */ 294 | private void assertValidState(HttpServletRequest request, HttpServletResponse response) throws InvalidRequestException { 295 | // TODO in v2: 296 | // - only store state/nonce in cookies, remove session storage 297 | // - create specific exception classes for various state validation failures (missing from auth response, missing 298 | // state cookie, mismatch) 299 | 300 | String stateFromRequest = request.getParameter(KEY_STATE); 301 | 302 | if (stateFromRequest == null) { 303 | throw new InvalidRequestException(INVALID_STATE_ERROR, "The received state doesn't match the expected one. No state parameter was found on the authorization response."); 304 | } 305 | 306 | // If response is null, check the Session. 307 | // This can happen when the deprecated handle method that only takes the request parameter is called 308 | if (response == null) { 309 | checkSessionState(request, stateFromRequest); 310 | return; 311 | } 312 | 313 | String cookieState = TransientCookieStore.getState(request, response); 314 | 315 | // Just in case state was stored in Session by building auth URL with deprecated method, but then called the 316 | // supported handle method with the request and response 317 | if (cookieState == null) { 318 | if (SessionUtils.get(request, StorageUtils.STATE_KEY) == null) { 319 | throw new InvalidRequestException(INVALID_STATE_ERROR, "The received state doesn't match the expected one. No state cookie or state session attribute found. Check that you are using non-deprecated methods and that cookies are not being removed on the server."); 320 | } 321 | checkSessionState(request, stateFromRequest); 322 | return; 323 | } 324 | 325 | if (!cookieState.equals(stateFromRequest)) { 326 | throw new InvalidRequestException(INVALID_STATE_ERROR, "The received state doesn't match the expected one."); 327 | } 328 | } 329 | 330 | private void checkSessionState(HttpServletRequest request, String stateFromRequest) throws InvalidRequestException { 331 | boolean valid = RandomStorage.checkSessionState(request, stateFromRequest); 332 | if (!valid) { 333 | throw new InvalidRequestException(INVALID_STATE_ERROR, "The received state doesn't match the expected one."); 334 | } 335 | } 336 | 337 | /** 338 | * Calls the Auth0 Authentication API to perform a Code Exchange. 339 | * 340 | * @param authorizationCode the code received on the login response. 341 | * @param redirectUri the redirect uri used on login request. 342 | * @return a new instance of {@link Tokens} with the received credentials. 343 | * @throws Auth0Exception if the request to the Auth0 server failed. 344 | * @see AuthAPI#exchangeCode(String, String) 345 | */ 346 | private Tokens exchangeCodeForTokens(String authorizationCode, String redirectUri) throws Auth0Exception { 347 | TokenHolder holder = client 348 | .exchangeCode(authorizationCode, redirectUri) 349 | .execute(); 350 | return new Tokens(holder.getAccessToken(), holder.getIdToken(), holder.getRefreshToken(), holder.getTokenType(), holder.getExpiresIn()); 351 | } 352 | 353 | /** 354 | * Used to keep the best version of each token. 355 | * It will prioritize the ID Token received in the front-channel, and the Access Token received in the code exchange request. 356 | * 357 | * @param frontChannelTokens the front-channel obtained tokens. 358 | * @param codeExchangeTokens the code-exchange obtained tokens. 359 | * @return a merged version of Tokens using the best tokens when possible. 360 | */ 361 | private Tokens mergeTokens(Tokens frontChannelTokens, Tokens codeExchangeTokens) { 362 | if (codeExchangeTokens == null) { 363 | return frontChannelTokens; 364 | } 365 | 366 | // Prefer access token from the code exchange 367 | String accessToken; 368 | String type; 369 | Long expiresIn; 370 | 371 | if (codeExchangeTokens.getAccessToken() != null) { 372 | accessToken = codeExchangeTokens.getAccessToken(); 373 | type = codeExchangeTokens.getType(); 374 | expiresIn = codeExchangeTokens.getExpiresIn(); 375 | } else { 376 | accessToken = frontChannelTokens.getAccessToken(); 377 | type = frontChannelTokens.getType(); 378 | expiresIn = frontChannelTokens.getExpiresIn(); 379 | } 380 | 381 | // Prefer ID token from the front-channel 382 | String idToken = frontChannelTokens.getIdToken() != null ? frontChannelTokens.getIdToken() : codeExchangeTokens.getIdToken(); 383 | 384 | // Refresh token only available from the code exchange 385 | String refreshToken = codeExchangeTokens.getRefreshToken(); 386 | 387 | return new Tokens(accessToken, idToken, refreshToken, type, expiresIn); 388 | } 389 | 390 | } -------------------------------------------------------------------------------- /src/main/java/com/auth0/SameSite.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | /** 4 | * Represents the values for the SameSite cookie attribute. 5 | * 6 | * @see OWASP - SameSite for additional information about the SameSite attribute.. 7 | */ 8 | enum SameSite { 9 | LAX("Lax"), 10 | NONE("None"), 11 | STRICT("Strict"); 12 | 13 | private String value; 14 | 15 | String getValue() { 16 | return this.value; 17 | } 18 | 19 | SameSite(String value) { 20 | this.value = value; 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/java/com/auth0/SessionUtils.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | import org.apache.commons.lang3.Validate; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.servlet.http.HttpSession; 7 | 8 | /** 9 | * Helper class to handle easy session key-value storage. 10 | */ 11 | @SuppressWarnings({"WeakerAccess", "unused"}) 12 | public abstract class SessionUtils { 13 | 14 | /** 15 | * Extracts the HttpSession from the given request. 16 | * 17 | * @param req a valid request to get the session from 18 | * @return the session of the request 19 | */ 20 | protected static HttpSession getSession(HttpServletRequest req) { 21 | return req.getSession(true); 22 | } 23 | 24 | /** 25 | * Set's the attribute value to the request session. 26 | * 27 | * @param req a valid request to get the session from 28 | * @param name the name of the attribute 29 | * @param value the value to set 30 | */ 31 | public static void set(HttpServletRequest req, String name, Object value) { 32 | Validate.notNull(req); 33 | Validate.notNull(name); 34 | getSession(req).setAttribute(name, value); 35 | } 36 | 37 | /** 38 | * Get the attribute with the given name from the request session. 39 | * 40 | * @param req a valid request to get the session from 41 | * @param name the name of the attribute 42 | * @return the attribute stored in the session or null if it doesn't exists 43 | */ 44 | public static Object get(HttpServletRequest req, String name) { 45 | Validate.notNull(req); 46 | Validate.notNull(name); 47 | return getSession(req).getAttribute(name); 48 | } 49 | 50 | /** 51 | * Same as {@link #get(HttpServletRequest, String)} but it also removes the value from the request session. 52 | * 53 | * @param req a valid request to get the session from 54 | * @param name the name of the attribute 55 | * @return the attribute stored in the session or null if it doesn't exists 56 | */ 57 | public static Object remove(HttpServletRequest req, String name) { 58 | Validate.notNull(req); 59 | Validate.notNull(name); 60 | Object value = get(req, name); 61 | getSession(req).removeAttribute(name); 62 | return value; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/auth0/SignatureVerifier.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | import com.auth0.jwt.JWT; 4 | import com.auth0.jwt.JWTVerifier; 5 | import com.auth0.jwt.exceptions.JWTDecodeException; 6 | import com.auth0.jwt.exceptions.JWTVerificationException; 7 | import com.auth0.jwt.exceptions.SignatureVerificationException; 8 | import com.auth0.jwt.interfaces.DecodedJWT; 9 | import org.apache.commons.lang3.Validate; 10 | 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | abstract class SignatureVerifier { 15 | 16 | private final JWTVerifier verifier; 17 | private final List acceptedAlgorithms; 18 | 19 | /** 20 | * Creates a new JWT Signature Verifier. 21 | * This instance will validate the token was signed using an expected algorithm 22 | * and then proceed to verify its signature 23 | * 24 | * @param verifier the instance that knows how to verify the signature. When null, the signature will not be checked. 25 | * @param algorithm the accepted algorithms. Must never be null! 26 | */ 27 | SignatureVerifier(JWTVerifier verifier, String... algorithm) { 28 | Validate.notEmpty(algorithm); 29 | this.verifier = verifier; 30 | this.acceptedAlgorithms = Arrays.asList(algorithm); 31 | } 32 | 33 | private DecodedJWT decodeToken(String token) throws TokenValidationException { 34 | try { 35 | return JWT.decode(token); 36 | } catch (JWTDecodeException e) { 37 | throw new TokenValidationException("ID token could not be decoded", e); 38 | } 39 | } 40 | 41 | DecodedJWT verifySignature(String token) throws TokenValidationException { 42 | DecodedJWT decoded = decodeToken(token); 43 | if (!this.acceptedAlgorithms.contains(decoded.getAlgorithm())) { 44 | throw new TokenValidationException(String.format("Signature algorithm of \"%s\" is not supported. Expected the ID token to be signed with \"%s\".", decoded.getAlgorithm(), this.acceptedAlgorithms)); 45 | } 46 | if (verifier != null) { 47 | try { 48 | verifier.verify(decoded); 49 | } catch (SignatureVerificationException e) { 50 | throw new TokenValidationException("Invalid token signature", e); 51 | } catch (JWTVerificationException ignored) { 52 | //NO-OP. Will be catch on a different step 53 | //Would only trigger for "expired tokens" (invalid exp) 54 | } 55 | } 56 | 57 | return decoded; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/auth0/StorageUtils.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | import org.apache.commons.codec.binary.Base64; 4 | 5 | import java.security.SecureRandom; 6 | 7 | class StorageUtils { 8 | 9 | private StorageUtils() {} 10 | 11 | static final String STATE_KEY = "com.auth0.state"; 12 | static final String NONCE_KEY = "com.auth0.nonce"; 13 | 14 | /** 15 | * Generates a new random string using {@link SecureRandom}. 16 | * The output can be used as State or Nonce values for API requests. 17 | * 18 | * @return a new random string. 19 | */ 20 | static String secureRandomString() { 21 | final SecureRandom sr = new SecureRandom(); 22 | final byte[] randomBytes = new byte[32]; 23 | sr.nextBytes(randomBytes); 24 | return Base64.encodeBase64URLSafeString(randomBytes); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/auth0/SymmetricSignatureVerifier.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | import com.auth0.jwt.JWT; 4 | import com.auth0.jwt.JWTVerifier; 5 | import com.auth0.jwt.algorithms.Algorithm; 6 | 7 | @SuppressWarnings("unused") 8 | class SymmetricSignatureVerifier extends SignatureVerifier { 9 | 10 | SymmetricSignatureVerifier(String secret) { 11 | super(createJWTVerifier(secret), "HS256"); 12 | } 13 | 14 | private static JWTVerifier createJWTVerifier(String secret) { 15 | Algorithm alg = Algorithm.HMAC256(secret); 16 | return JWT.require(alg) 17 | .ignoreIssuedAt() 18 | .build(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/auth0/TokenValidationException.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | class TokenValidationException extends RuntimeException { 4 | 5 | TokenValidationException(String message) { 6 | super(message); 7 | } 8 | 9 | TokenValidationException(String message, Throwable cause) { 10 | super(message, cause); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/auth0/Tokens.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Wrapper for the the user's credentials returned by Auth0. 7 | *

    8 | *
  • accessToken: Access Token for Auth0 API
  • 9 | *
  • idToken: Identity Token with user information
  • 10 | *
  • refreshToken: Refresh Token that can be used to request new tokens without signing in again
  • 11 | *
  • type: Token Type
  • 12 | *
  • expiresIn: Token expiration
  • 13 | *
14 | */ 15 | @SuppressWarnings({"unused", "WeakerAccess"}) 16 | public class Tokens implements Serializable { 17 | 18 | private static final long serialVersionUID = 2371882820082543721L; 19 | 20 | private final String accessToken; 21 | private final String idToken; 22 | private final String refreshToken; 23 | private final String type; 24 | private final Long expiresIn; 25 | 26 | /** 27 | * @param accessToken access token for Auth0 API 28 | * @param idToken identity token with user information 29 | * @param refreshToken refresh token that can be used to request new tokens without signing in again 30 | * @param type token type 31 | * @param expiresIn token expiration 32 | */ 33 | public Tokens(String accessToken, String idToken, String refreshToken, String type, Long expiresIn) { 34 | this.accessToken = accessToken; 35 | this.idToken = idToken; 36 | this.refreshToken = refreshToken; 37 | this.type = type; 38 | this.expiresIn = expiresIn; 39 | } 40 | 41 | /** 42 | * Getter for the Access Token. 43 | * 44 | * @return the Access Token. 45 | */ 46 | public String getAccessToken() { 47 | return accessToken; 48 | } 49 | 50 | /** 51 | * Getter for the Id Token. 52 | * 53 | * @return the Id Token. 54 | */ 55 | public String getIdToken() { 56 | return idToken; 57 | } 58 | 59 | /** 60 | * Getter for the Refresh Token. 61 | * 62 | * @return the Refresh Token. 63 | */ 64 | public String getRefreshToken() { 65 | return refreshToken; 66 | } 67 | 68 | /** 69 | * Getter for the token Type . 70 | * 71 | * @return the Type of the token. 72 | */ 73 | public String getType() { 74 | return type; 75 | } 76 | 77 | /** 78 | * Getter for the Expiration time of the Token. 79 | * 80 | * @return the expiration time. 81 | */ 82 | public Long getExpiresIn() { 83 | return expiresIn; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/auth0/TransientCookieStore.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | import org.apache.commons.lang3.Validate; 4 | 5 | import javax.servlet.http.Cookie; 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | import java.io.UnsupportedEncodingException; 9 | import java.net.URLDecoder; 10 | import java.nio.charset.StandardCharsets; 11 | 12 | /** 13 | * Allows storage and retrieval/removal of cookies. 14 | */ 15 | class TransientCookieStore { 16 | 17 | // Prevent instantiation 18 | private TransientCookieStore() {} 19 | 20 | 21 | /** 22 | * Stores a state value as a cookie on the response. 23 | * 24 | * @param response the response object to set the cookie on 25 | * @param state the value for the state cookie. If null, no cookie will be set. 26 | * @param sameSite the value for the SameSite attribute on the cookie 27 | * @param useLegacySameSiteCookie whether to set a fallback cookie or not 28 | * @param isSecureCookie whether to always set the Secure cookie attribute or not 29 | */ 30 | static void storeState(HttpServletResponse response, String state, SameSite sameSite, boolean useLegacySameSiteCookie, boolean isSecureCookie, String cookiePath) { 31 | store(response, StorageUtils.STATE_KEY, state, sameSite, useLegacySameSiteCookie, isSecureCookie, cookiePath); 32 | } 33 | 34 | /** 35 | * Stores a nonce value as a cookie on the response. 36 | * 37 | * @param response the response object to set the cookie on 38 | * @param nonce the value for the nonce cookie. If null, no cookie will be set. 39 | * @param sameSite the value for the SameSite attribute on the cookie 40 | * @param useLegacySameSiteCookie whether to set a fallback cookie or not 41 | * @param isSecureCookie whether to always set the Secure cookie attribute or not 42 | */ 43 | static void storeNonce(HttpServletResponse response, String nonce, SameSite sameSite, boolean useLegacySameSiteCookie, boolean isSecureCookie, String cookiePath) { 44 | store(response, StorageUtils.NONCE_KEY, nonce, sameSite, useLegacySameSiteCookie, isSecureCookie, cookiePath); 45 | } 46 | 47 | /** 48 | * Gets the value associated with the state cookie and removes it. 49 | * 50 | * @param request the request object 51 | * @param response the response object 52 | * @return the value of the state cookie, if it exists 53 | */ 54 | static String getState(HttpServletRequest request, HttpServletResponse response) { 55 | return getOnce(StorageUtils.STATE_KEY, request, response); 56 | } 57 | 58 | /** 59 | * Gets the value associated with the nonce cookie and removes it. 60 | * 61 | * @param request the request object 62 | * @param response the response object 63 | * @return the value of the nonce cookie, if it exists 64 | */ 65 | static String getNonce(HttpServletRequest request, HttpServletResponse response) { 66 | return getOnce(StorageUtils.NONCE_KEY, request, response); 67 | } 68 | 69 | private static void store(HttpServletResponse response, String key, String value, SameSite sameSite, boolean useLegacySameSiteCookie, boolean isSecureCookie, String cookiePath) { 70 | Validate.notNull(response, "response must not be null"); 71 | Validate.notNull(key, "key must not be null"); 72 | Validate.notNull(sameSite, "sameSite must not be null"); 73 | 74 | if (value == null) { 75 | return; 76 | } 77 | 78 | boolean isSameSiteNone = SameSite.NONE == sameSite; 79 | 80 | AuthCookie sameSiteCookie = new AuthCookie(key, value); 81 | sameSiteCookie.setSameSite(sameSite); 82 | sameSiteCookie.setSecure(isSameSiteNone || isSecureCookie); 83 | if (cookiePath != null) { 84 | sameSiteCookie.setPath(cookiePath); 85 | } 86 | 87 | // Servlet Cookie API does not yet support setting the SameSite attribute, so just set cookie on header 88 | response.addHeader("Set-Cookie", sameSiteCookie.buildHeaderString()); 89 | 90 | // set legacy fallback cookie (if configured) for clients that won't accept SameSite=None 91 | if (isSameSiteNone && useLegacySameSiteCookie) { 92 | AuthCookie legacyCookie = new AuthCookie("_" + key, value); 93 | legacyCookie.setSecure(isSecureCookie); 94 | response.addHeader("Set-Cookie", legacyCookie.buildHeaderString()); 95 | } 96 | 97 | } 98 | 99 | private static String getOnce(String cookieName, HttpServletRequest request, HttpServletResponse response) { 100 | Cookie[] requestCookies = request.getCookies(); 101 | if (requestCookies == null) { 102 | return null; 103 | } 104 | 105 | Cookie foundCookie = null; 106 | for (Cookie c : requestCookies) { 107 | if (cookieName.equals(c.getName())) { 108 | foundCookie = c; 109 | break; 110 | } 111 | } 112 | 113 | String foundCookieVal = null; 114 | if (foundCookie != null) { 115 | foundCookieVal = decode(foundCookie.getValue()); 116 | delete(foundCookie, response); 117 | } 118 | 119 | Cookie foundLegacyCookie = null; 120 | for (Cookie c : requestCookies) { 121 | if (("_" + cookieName).equals(c.getName())) { 122 | foundLegacyCookie = c; 123 | break; 124 | } 125 | } 126 | 127 | String foundLegacyCookieVal = null; 128 | if (foundLegacyCookie != null) { 129 | foundLegacyCookieVal = decode(foundLegacyCookie.getValue()); 130 | delete(foundLegacyCookie, response); 131 | } 132 | 133 | return foundCookieVal != null ? foundCookieVal : foundLegacyCookieVal; 134 | } 135 | 136 | private static void delete(Cookie cookie, HttpServletResponse response) { 137 | cookie.setMaxAge(0); 138 | cookie.setValue(""); 139 | response.addCookie(cookie); 140 | } 141 | 142 | private static String decode(String valueToDecode) { 143 | try { 144 | return URLDecoder.decode(valueToDecode, StandardCharsets.UTF_8.name()); 145 | } catch (UnsupportedEncodingException e) { 146 | throw new AssertionError("UTF-8 character set not supported", e.getCause()); 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/test/java/com/auth0/AuthorizeUrlTest.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | import com.auth0.client.HttpOptions; 4 | import com.auth0.client.auth.AuthAPI; 5 | import com.auth0.exception.Auth0Exception; 6 | import com.auth0.json.auth.PushedAuthorizationResponse; 7 | import com.auth0.net.Request; 8 | import okhttp3.HttpUrl; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Test; 11 | import org.springframework.mock.web.MockHttpServletRequest; 12 | import org.springframework.mock.web.MockHttpServletResponse; 13 | 14 | import javax.servlet.http.HttpServletRequest; 15 | import javax.servlet.http.HttpServletResponse; 16 | import java.util.Collection; 17 | import java.util.Map; 18 | 19 | import static org.hamcrest.CoreMatchers.*; 20 | import static org.hamcrest.MatcherAssert.assertThat; 21 | import static org.junit.jupiter.api.Assertions.assertEquals; 22 | import static org.junit.jupiter.api.Assertions.assertThrows; 23 | import static org.mockito.ArgumentMatchers.*; 24 | import static org.mockito.Mockito.mock; 25 | import static org.mockito.Mockito.when; 26 | 27 | public class AuthorizeUrlTest { 28 | 29 | private AuthAPI client; 30 | private HttpServletResponse response; 31 | private HttpServletRequest request; 32 | 33 | @BeforeEach 34 | public void setUp() { 35 | client = new AuthAPI("domain.auth0.com", "clientId", "clientSecret"); 36 | request = new MockHttpServletRequest(); 37 | response = new MockHttpServletResponse(); 38 | } 39 | 40 | @Test 41 | public void shouldBuildValidStringUrl() { 42 | String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") 43 | .build(); 44 | assertThat(url, is(notNullValue())); 45 | assertThat(HttpUrl.parse(url), is(notNullValue())); 46 | } 47 | 48 | @Test 49 | public void shouldSetDefaultScope() { 50 | String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") 51 | .build(); 52 | assertThat(HttpUrl.parse(url).queryParameter("scope"), is("openid")); 53 | } 54 | 55 | @Test 56 | public void shouldSetResponseType() { 57 | String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") 58 | .build(); 59 | assertThat(HttpUrl.parse(url).queryParameter("response_type"), is("id_token token")); 60 | } 61 | 62 | @Test 63 | public void shouldSetRedirectUrl() { 64 | String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") 65 | .build(); 66 | assertThat(HttpUrl.parse(url).queryParameter("redirect_uri"), is("https://redirect.to/me")); 67 | } 68 | 69 | @Test 70 | public void shouldSetConnection() { 71 | String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") 72 | .withConnection("facebook") 73 | .build(); 74 | assertThat(HttpUrl.parse(url).queryParameter("connection"), is("facebook")); 75 | } 76 | 77 | @Test 78 | public void shouldSetAudience() { 79 | String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") 80 | .withAudience("https://api.auth0.com/") 81 | .build(); 82 | assertThat(HttpUrl.parse(url).queryParameter("audience"), is("https://api.auth0.com/")); 83 | } 84 | 85 | @Test 86 | public void shouldSetNonceSameSiteAndLegacyCookieByDefault() { 87 | String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") 88 | .withNonce("asdfghjkl") 89 | .build(); 90 | assertThat(HttpUrl.parse(url).queryParameter("nonce"), is("asdfghjkl")); 91 | 92 | Collection headers = response.getHeaders("Set-Cookie"); 93 | assertThat(headers.size(), is(2)); 94 | assertThat(headers, hasItem("com.auth0.nonce=asdfghjkl; HttpOnly; Max-Age=600; SameSite=None; Secure")); 95 | assertThat(headers, hasItem("_com.auth0.nonce=asdfghjkl; HttpOnly; Max-Age=600")); 96 | } 97 | 98 | @Test 99 | public void shouldSetNonceSameSiteAndNotLegacyCookieWhenConfigured() { 100 | String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") 101 | .withNonce("asdfghjkl") 102 | .withLegacySameSiteCookie(false) 103 | .build(); 104 | assertThat(HttpUrl.parse(url).queryParameter("nonce"), is("asdfghjkl")); 105 | 106 | Collection headers = response.getHeaders("Set-Cookie"); 107 | assertThat(headers.size(), is(1)); 108 | assertThat(headers, hasItem("com.auth0.nonce=asdfghjkl; HttpOnly; Max-Age=600; SameSite=None; Secure")); 109 | } 110 | 111 | @Test 112 | public void shouldSetStateSameSiteAndLegacyCookieByDefault() { 113 | String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") 114 | .withState("asdfghjkl") 115 | .build(); 116 | assertThat(HttpUrl.parse(url).queryParameter("state"), is("asdfghjkl")); 117 | 118 | Collection headers = response.getHeaders("Set-Cookie"); 119 | assertThat(headers.size(), is(2)); 120 | assertThat(headers, hasItem("com.auth0.state=asdfghjkl; HttpOnly; Max-Age=600; SameSite=None; Secure")); 121 | assertThat(headers, hasItem("_com.auth0.state=asdfghjkl; HttpOnly; Max-Age=600")); 122 | } 123 | 124 | @Test 125 | public void shouldSetStateSameSiteAndNotLegacyCookieWhenConfigured() { 126 | String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") 127 | .withState("asdfghjkl") 128 | .withLegacySameSiteCookie(false) 129 | .build(); 130 | assertThat(HttpUrl.parse(url).queryParameter("state"), is("asdfghjkl")); 131 | 132 | Collection headers = response.getHeaders("Set-Cookie"); 133 | assertThat(headers.size(), is(1)); 134 | assertThat(headers, hasItem("com.auth0.state=asdfghjkl; HttpOnly; Max-Age=600; SameSite=None; Secure")); 135 | } 136 | 137 | @Test 138 | public void shouldSetSecureCookieWhenConfiguredTrue() { 139 | String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "code") 140 | .withState("asdfghjkl") 141 | .withSecureCookie(true) 142 | .build(); 143 | assertThat(HttpUrl.parse(url).queryParameter("state"), is("asdfghjkl")); 144 | 145 | Collection headers = response.getHeaders("Set-Cookie"); 146 | assertThat(headers.size(), is(1)); 147 | assertThat(headers, hasItem("com.auth0.state=asdfghjkl; HttpOnly; Max-Age=600; SameSite=Lax; Secure")); 148 | } 149 | 150 | @Test 151 | public void shouldSetSecureCookieWhenConfiguredFalseAndSameSiteNone() { 152 | String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token") 153 | .withState("asdfghjkl") 154 | .withSecureCookie(false) 155 | .build(); 156 | assertThat(HttpUrl.parse(url).queryParameter("state"), is("asdfghjkl")); 157 | 158 | Collection headers = response.getHeaders("Set-Cookie"); 159 | assertThat(headers.size(), is(2)); 160 | assertThat(headers, hasItem("com.auth0.state=asdfghjkl; HttpOnly; Max-Age=600; SameSite=None; Secure")); 161 | assertThat(headers, hasItem("_com.auth0.state=asdfghjkl; HttpOnly; Max-Age=600")); 162 | } 163 | 164 | @Test 165 | public void shouldSetNoCookiesWhenNonceAndStateNotSet() { 166 | String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") 167 | .build(); 168 | assertThat(HttpUrl.parse(url).queryParameter("state"), nullValue()); 169 | assertThat(HttpUrl.parse(url).queryParameter("nonce"), nullValue()); 170 | 171 | Collection headers = response.getHeaders("Set-Cookie"); 172 | assertThat(headers.size(), is(0)); 173 | } 174 | 175 | @Test 176 | public void shouldSetNoSessionValuesWhenNonceAndStateNotSet() { 177 | String url = new AuthorizeUrl(client, request, null, "https://redirect.to/me", "id_token token") 178 | .build(); 179 | assertThat(HttpUrl.parse(url).queryParameter("state"), nullValue()); 180 | assertThat(HttpUrl.parse(url).queryParameter("nonce"), nullValue()); 181 | 182 | Collection headers = response.getHeaders("Set-Cookie"); 183 | assertThat(headers.size(), is(0)); 184 | } 185 | 186 | @Test 187 | public void shouldSetScope() { 188 | String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") 189 | .withScope("openid profile email") 190 | .build(); 191 | assertThat(HttpUrl.parse(url).queryParameter("scope"), is("openid profile email")); 192 | } 193 | 194 | @Test 195 | public void shouldSetCustomParameterScope() { 196 | String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") 197 | .withParameter("custom", "value") 198 | .build(); 199 | assertThat(HttpUrl.parse(url).queryParameter("custom"), is("value")); 200 | } 201 | 202 | @Test 203 | public void shouldThrowWhenReusingTheInstance() { 204 | AuthorizeUrl builder = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token"); 205 | String firstCall = builder.build(); 206 | assertThat(firstCall, is(notNullValue())); 207 | IllegalStateException e = assertThrows(IllegalStateException.class, builder::build); 208 | assertEquals("The AuthorizeUrl instance must not be reused.", e.getMessage()); 209 | } 210 | 211 | @Test 212 | public void shouldThrowWhenChangingTheRedirectURI() { 213 | IllegalArgumentException e = assertThrows( 214 | IllegalArgumentException.class, 215 | () -> new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") 216 | .withParameter("redirect_uri", "new_value")); 217 | assertEquals("Redirect URI cannot be changed once set.", e.getMessage()); 218 | } 219 | 220 | @Test 221 | public void shouldThrowWhenChangingTheResponseType() { 222 | IllegalArgumentException e = assertThrows( 223 | IllegalArgumentException.class, 224 | () -> new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") 225 | .withParameter("response_type", "new_value")); 226 | assertEquals("Response type cannot be changed once set.", e.getMessage()); 227 | } 228 | 229 | @Test 230 | public void shouldThrowWhenChangingTheStateUsingCustomParameterSetter() { 231 | IllegalArgumentException e = assertThrows( 232 | IllegalArgumentException.class, 233 | () -> new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") 234 | .withParameter("state", "new_value")); 235 | assertEquals("Please, use the dedicated methods for setting the 'nonce' and 'state' parameters.", e.getMessage()); 236 | } 237 | 238 | @Test 239 | public void shouldThrowWhenChangingTheNonceUsingCustomParameterSetter() { 240 | IllegalArgumentException e = assertThrows( 241 | IllegalArgumentException.class, 242 | () -> new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") 243 | .withParameter("nonce", "new_value")); 244 | assertEquals("Please, use the dedicated methods for setting the 'nonce' and 'state' parameters.", e.getMessage()); 245 | } 246 | 247 | @Test 248 | public void shouldGetAuthorizeUrlFromPAR() throws Exception { 249 | AuthAPIStub authAPIStub = new AuthAPIStub("https://domain.com", "clientId", "clientSecret"); 250 | Request requestMock = mock(Request.class); 251 | 252 | when(requestMock.execute()).thenReturn(new PushedAuthorizationResponse("urn:example:bwc4JK-ESC0w8acc191e-Y1LTC2", 90)); 253 | 254 | authAPIStub.pushedAuthorizationResponseRequest = requestMock; 255 | String url = new AuthorizeUrl(authAPIStub, request, response, "https://domain.com/callback", "code") 256 | .fromPushedAuthorizationRequest(); 257 | 258 | assertThat(url, is("https://domain.com/authorize?client_id=clientId&request_uri=urn%3Aexample%3Abwc4JK-ESC0w8acc191e-Y1LTC2")); 259 | } 260 | 261 | @Test 262 | public void fromPushedAuthorizationRequestThrowsWhenRequestUriIsNull() throws Exception { 263 | AuthAPIStub authAPIStub = new AuthAPIStub("https://domain.com", "clientId", "clientSecret"); 264 | Request requestMock = mock(Request.class); 265 | when(requestMock.execute()).thenReturn(new PushedAuthorizationResponse(null, 90)); 266 | 267 | authAPIStub.pushedAuthorizationResponseRequest = requestMock; 268 | 269 | InvalidRequestException exception = assertThrows(InvalidRequestException.class, () -> { 270 | new AuthorizeUrl(authAPIStub, request, response, "https://domain.com/callback", "code") 271 | .fromPushedAuthorizationRequest(); 272 | }); 273 | 274 | assertThat(exception.getMessage(), is("The PAR request returned a missing or empty request_uri value")); 275 | } 276 | 277 | @Test 278 | public void fromPushedAuthorizationRequestThrowsWhenRequestUriIsEmpty() throws Exception { 279 | AuthAPIStub authAPIStub = new AuthAPIStub("https://domain.com", "clientId", "clientSecret"); 280 | Request requestMock = mock(Request.class); 281 | when(requestMock.execute()).thenReturn(new PushedAuthorizationResponse("urn:example:bwc4JK-ESC0w8acc191e-Y1LTC2", null)); 282 | 283 | authAPIStub.pushedAuthorizationResponseRequest = requestMock; 284 | 285 | InvalidRequestException exception = assertThrows(InvalidRequestException.class, () -> { 286 | new AuthorizeUrl(authAPIStub, request, response, "https://domain.com/callback", "code") 287 | .fromPushedAuthorizationRequest(); 288 | }); 289 | 290 | assertThat(exception.getMessage(), is("The PAR request returned a missing expires_in value")); 291 | } 292 | 293 | @Test 294 | public void fromPushedAuthorizationRequestThrowsWhenExpiresInIsNull() throws Exception { 295 | AuthAPIStub authAPIStub = new AuthAPIStub("https://domain.com", "clientId", "clientSecret"); 296 | Request requestMock = mock(Request.class); 297 | when(requestMock.execute()).thenReturn(new PushedAuthorizationResponse(null, 90)); 298 | 299 | authAPIStub.pushedAuthorizationResponseRequest = requestMock; 300 | 301 | InvalidRequestException exception = assertThrows(InvalidRequestException.class, () -> { 302 | new AuthorizeUrl(authAPIStub, request, response, "https://domain.com/callback", "code") 303 | .fromPushedAuthorizationRequest(); 304 | }); 305 | 306 | assertThat(exception.getMessage(), is("The PAR request returned a missing or empty request_uri value")); 307 | } 308 | 309 | @Test 310 | public void fromPushedAuthorizationRequestThrowsWhenRequestThrows() throws Exception { 311 | AuthAPI authAPIMock = mock(AuthAPI.class); 312 | Request requestMock = mock(Request.class); 313 | 314 | when(requestMock.execute()) 315 | .thenThrow(new Auth0Exception("error")); 316 | when(authAPIMock.pushedAuthorizationRequest(eq("https://domain.com/callback"), eq("code"), anyMap())) 317 | .thenReturn(requestMock); 318 | 319 | InvalidRequestException exception = assertThrows(InvalidRequestException.class, () -> { 320 | new AuthorizeUrl(authAPIMock, request, response, "https://domain.com/callback", "code") 321 | .fromPushedAuthorizationRequest(); 322 | }); 323 | 324 | assertThat(exception.getMessage(), is("error")); 325 | assertThat(exception.getCause(), instanceOf(Auth0Exception.class)); 326 | } 327 | 328 | static class AuthAPIStub extends AuthAPI { 329 | 330 | Request pushedAuthorizationResponseRequest; 331 | 332 | public AuthAPIStub(String domain, String clientId, String clientSecret, HttpOptions options) { 333 | super(domain, clientId, clientSecret, options); 334 | } 335 | 336 | public AuthAPIStub(String domain, String clientId, String clientSecret) { 337 | super(domain, clientId, clientSecret); 338 | } 339 | 340 | @Override 341 | public Request pushedAuthorizationRequest(String redirectUri, String responseType, Map params) { 342 | return pushedAuthorizationResponseRequest; 343 | } 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /src/test/java/com/auth0/IdentityVerificationExceptionMatcher.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | import org.hamcrest.Description; 4 | import org.hamcrest.TypeSafeMatcher; 5 | 6 | public class IdentityVerificationExceptionMatcher extends TypeSafeMatcher { 7 | 8 | public static IdentityVerificationExceptionMatcher hasCode(String code) { 9 | return new IdentityVerificationExceptionMatcher(code); 10 | } 11 | 12 | private String code; 13 | private final String expectedCode; 14 | 15 | private IdentityVerificationExceptionMatcher(String expectedCode) { 16 | this.expectedCode = expectedCode; 17 | } 18 | 19 | @Override 20 | protected boolean matchesSafely(IdentityVerificationException exception) { 21 | code = exception.getCode(); 22 | return code.equalsIgnoreCase(expectedCode); 23 | } 24 | 25 | @Override 26 | public void describeTo(Description description) { 27 | description.appendValue(code) 28 | .appendText(" was not found instead of ") 29 | .appendValue(expectedCode); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/com/auth0/IdentityVerificationExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.hamcrest.core.Is.is; 7 | import static org.hamcrest.MatcherAssert.assertThat; 8 | import static org.mockito.Mockito.mock; 9 | 10 | public class IdentityVerificationExceptionTest { 11 | private Throwable cause; 12 | private IdentityVerificationException exception; 13 | 14 | @BeforeEach 15 | public void setUp() { 16 | cause = mock(Throwable.class); 17 | exception = new IdentityVerificationException("error", "description", cause); 18 | } 19 | 20 | @Test 21 | public void shouldGetCode() { 22 | assertThat(exception.getCode(), is("error")); 23 | } 24 | 25 | @Test 26 | public void shouldGetDescription() { 27 | assertThat(exception.getMessage(), is("description")); 28 | } 29 | 30 | @Test 31 | public void shouldGetCause() { 32 | assertThat(exception.getCause(), is(cause)); 33 | } 34 | 35 | @Test 36 | public void shouldBeAPIError() { 37 | IdentityVerificationException exception = new IdentityVerificationException("a0.api_error", "description", null); 38 | assertThat(exception.isAPIError(), is(true)); 39 | } 40 | 41 | @Test 42 | public void shouldBeJWTError() { 43 | IdentityVerificationException exception = new IdentityVerificationException("a0.missing_jwt_public_key_error", "description", null); 44 | assertThat(exception.isJWTError(), is(true)); 45 | IdentityVerificationException exception2 = new IdentityVerificationException("a0.invalid_jwt_error", "description", null); 46 | assertThat(exception2.isJWTError(), is(true)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/auth0/InvalidRequestExceptionMatcher.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | import org.hamcrest.Description; 4 | import org.hamcrest.TypeSafeMatcher; 5 | 6 | public class InvalidRequestExceptionMatcher extends TypeSafeMatcher { 7 | 8 | private final String expectedCode; 9 | 10 | public static InvalidRequestExceptionMatcher hasCode(String code) { 11 | return new InvalidRequestExceptionMatcher(code); 12 | } 13 | 14 | private String code; 15 | 16 | private InvalidRequestExceptionMatcher(String expectedCode) { 17 | this.expectedCode = expectedCode; 18 | } 19 | 20 | @Override 21 | protected boolean matchesSafely(InvalidRequestException exception) { 22 | code = exception.getCode(); 23 | 24 | if (expectedCode != null) { 25 | return expectedCode.equals(code); 26 | } 27 | return false; 28 | } 29 | 30 | @Override 31 | public void describeTo(Description description) { 32 | description.appendValue(code) 33 | .appendText(" was not found instead of ") 34 | .appendValue(expectedCode); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/com/auth0/InvalidRequestExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.hamcrest.core.Is.is; 7 | import static org.hamcrest.MatcherAssert.assertThat; 8 | 9 | public class InvalidRequestExceptionTest { 10 | 11 | private InvalidRequestException exception; 12 | 13 | @BeforeEach 14 | public void setUp() { 15 | exception = new InvalidRequestException("error", "message"); 16 | } 17 | 18 | @SuppressWarnings("deprecation") 19 | @Test 20 | public void shouldGetDescription() { 21 | assertThat(exception.getDescription(), is("message")); 22 | } 23 | 24 | @Test 25 | public void shouldGetCode() { 26 | assertThat(exception.getCode(), is("error")); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/auth0/RandomStorageTest.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.mock.web.MockHttpServletRequest; 5 | 6 | import static org.hamcrest.CoreMatchers.is; 7 | import static org.hamcrest.CoreMatchers.nullValue; 8 | import static org.hamcrest.MatcherAssert.assertThat; 9 | 10 | public class RandomStorageTest { 11 | 12 | @Test 13 | public void shouldSetState() { 14 | MockHttpServletRequest req = new MockHttpServletRequest(); 15 | 16 | RandomStorage.setSessionState(req, "123456"); 17 | assertThat(req.getSession().getAttribute("com.auth0.state"), is("123456")); 18 | } 19 | 20 | @Test 21 | public void shouldAcceptBothNullStates() { 22 | MockHttpServletRequest req = new MockHttpServletRequest(); 23 | boolean validState = RandomStorage.checkSessionState(req, null); 24 | assertThat(validState, is(true)); 25 | } 26 | 27 | @Test 28 | public void shouldFailIfSessionStateIsNullButCurrentStateNotNull() { 29 | MockHttpServletRequest req = new MockHttpServletRequest(); 30 | boolean validState = RandomStorage.checkSessionState(req, "12345"); 31 | assertThat(validState, is(false)); 32 | } 33 | 34 | @Test 35 | public void shouldCheckAndRemoveInvalidState() { 36 | MockHttpServletRequest req = new MockHttpServletRequest(); 37 | req.getSession().setAttribute("com.auth0.state", "123456"); 38 | 39 | boolean validState = RandomStorage.checkSessionState(req, "abcdef"); 40 | assertThat(validState, is(false)); 41 | assertThat(req.getSession().getAttribute("com.auth0.state"), is(nullValue())); 42 | } 43 | 44 | @Test 45 | public void shouldCheckAndRemoveCorrectState() { 46 | MockHttpServletRequest req = new MockHttpServletRequest(); 47 | req.getSession().setAttribute("com.auth0.state", "123456"); 48 | 49 | boolean validState = RandomStorage.checkSessionState(req, "123456"); 50 | assertThat(validState, is(true)); 51 | assertThat(req.getSession().getAttribute("com.auth0.state"), is(nullValue())); 52 | } 53 | 54 | @Test 55 | public void shouldSetNonce() { 56 | MockHttpServletRequest req = new MockHttpServletRequest(); 57 | 58 | RandomStorage.setSessionNonce(req, "123456"); 59 | assertThat(req.getSession().getAttribute("com.auth0.nonce"), is("123456")); 60 | } 61 | 62 | @Test 63 | public void shouldGetAndRemoveNonce() { 64 | MockHttpServletRequest req = new MockHttpServletRequest(); 65 | req.getSession().setAttribute("com.auth0.nonce", "123456"); 66 | 67 | String nonce = RandomStorage.removeSessionNonce(req); 68 | assertThat(nonce, is("123456")); 69 | assertThat(req.getSession().getAttribute("com.auth0.nonce"), is(nullValue())); 70 | } 71 | 72 | @Test 73 | public void shouldGetAndRemoveNonceIfMissing() { 74 | MockHttpServletRequest req = new MockHttpServletRequest(); 75 | 76 | String nonce = RandomStorage.removeSessionNonce(req); 77 | assertThat(nonce, is(nullValue())); 78 | assertThat(req.getSession().getAttribute("com.auth0.nonce"), is(nullValue())); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/com/auth0/SessionUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.mock.web.MockHttpServletRequest; 5 | 6 | import static org.hamcrest.CoreMatchers.is; 7 | import static org.hamcrest.CoreMatchers.nullValue; 8 | import static org.hamcrest.MatcherAssert.assertThat; 9 | 10 | public class SessionUtilsTest { 11 | @Test 12 | public void shouldGetAndRemoveAttribute() { 13 | MockHttpServletRequest req = new MockHttpServletRequest(); 14 | req.getSession().setAttribute("name", "value"); 15 | 16 | assertThat(SessionUtils.remove(req, "name"), is("value")); 17 | assertThat(req.getSession().getAttribute("name"), is(nullValue())); 18 | } 19 | 20 | @Test 21 | public void shouldGetAttribute() { 22 | MockHttpServletRequest req = new MockHttpServletRequest(); 23 | req.getSession().setAttribute("name", "value"); 24 | 25 | assertThat(SessionUtils.get(req, "name"), is("value")); 26 | assertThat(req.getSession().getAttribute("name"), is("value")); 27 | } 28 | 29 | @Test 30 | public void shouldGetNullAttributeIfMissing() { 31 | MockHttpServletRequest req = new MockHttpServletRequest(); 32 | 33 | assertThat(SessionUtils.get(req, "name"), is(nullValue())); 34 | assertThat(req.getSession().getAttribute("name"), is(nullValue())); 35 | } 36 | 37 | @Test 38 | public void shouldSetAttribute() { 39 | MockHttpServletRequest req = new MockHttpServletRequest(); 40 | 41 | SessionUtils.set(req, "name", "value"); 42 | assertThat(req.getSession().getAttribute("name"), is("value")); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/com/auth0/SignatureVerifierTest.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | import com.auth0.jwk.Jwk; 4 | import com.auth0.jwk.JwkException; 5 | import com.auth0.jwk.JwkProvider; 6 | import com.auth0.jwt.interfaces.DecodedJWT; 7 | import org.bouncycastle.util.io.pem.PemReader; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import java.io.FileInputStream; 11 | import java.io.FileReader; 12 | import java.io.IOException; 13 | import java.nio.file.Paths; 14 | import java.security.KeyFactory; 15 | import java.security.PublicKey; 16 | import java.security.cert.CertificateFactory; 17 | import java.security.cert.X509Certificate; 18 | import java.security.interfaces.RSAPublicKey; 19 | import java.security.spec.EncodedKeySpec; 20 | import java.security.spec.X509EncodedKeySpec; 21 | import java.util.Scanner; 22 | 23 | import static org.hamcrest.CoreMatchers.notNullValue; 24 | import static org.hamcrest.MatcherAssert.assertThat; 25 | import static org.junit.jupiter.api.Assertions.assertEquals; 26 | import static org.junit.jupiter.api.Assertions.assertThrows; 27 | import static org.mockito.Mockito.mock; 28 | import static org.mockito.Mockito.when; 29 | 30 | public class SignatureVerifierTest { 31 | 32 | private static final String EXPIRED_HS_JWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJub25jZSI6IjEyMzQiLCJpc3MiOiJodHRwczovL21lLmF1dGgwLmNvbS8iLCJhdWQiOiJkYU9nbkdzUlloa3d1NjIxdmYiLCJzdWIiOiJhdXRoMHx1c2VyMTIzIiwiZXhwIjo5NzE3ODkzMTd9.5_VOXBmOVMSi8OGgonyfyiJSq3A03PwOEuZlPD-Gxik"; 33 | private static final String NONE_JWT = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJub25jZSI6IjEyMzQiLCJpc3MiOiJodHRwczovL21lLmF1dGgwLmNvbS8iLCJhdWQiOiJkYU9nbkdzUlloa3d1NjIxdmYiLCJzdWIiOiJhdXRoMHx1c2VyMTIzIn0."; 34 | private static final String HS_JWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJub25jZSI6IjEyMzQiLCJpc3MiOiJodHRwczovL21lLmF1dGgwLmNvbS8iLCJhdWQiOiJkYU9nbkdzUlloa3d1NjIxdmYiLCJzdWIiOiJhdXRoMHx1c2VyMTIzIn0.a7ayNmFTxS2D-EIoUikoJ6dck7I8veWyxnje_mYD3qY"; 35 | private static final String RS_JWT = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFiYzEyMyJ9.eyJub25jZSI6IjEyMzQiLCJpc3MiOiJodHRwczovL21lLmF1dGgwLmNvbS8iLCJhdWQiOiJkYU9nbkdzUlloa3d1NjIxdmYiLCJzdWIiOiJhdXRoMHx1c2VyMTIzIn0.PkPWdoZNfXz8EB0SBPH83lNSOhyhdhdqYIgIwgY2nHozUnFOaUjVewlAXxP_3LBGibQ_ng4s5fEEOCJjaKBy04McryvOuL6nqb1dPQseeyxuv2zQitfrs-7kEtfeS3umywM-tV6guw9_W3nmIgaXOiYiF4WJM23ItbdCmvwdXLaf9-xHkQbRY_zEwEFbprFttKUXFbkPt6XjZ3zZwZbNZn64bx2PBiSJ2KMZAE3Lghmci-RXdhi7hXpmN30Tzze1ZsjvVeRRKNzShByKK9ZGZPmQ5yggJOXFy32ehjGkYwFMCqgMQomcGbcYhsd97huKHMHl3HOE5GDYjIq9o9oKRA"; 36 | private static final String RS_PUBLIC_KEY = "src/test/resources/public.pem"; 37 | private static final String RS_PUBLIC_KEY_BAD = "src/test/resources/bad-public.pem"; 38 | private static final String RS_JWT_INVALID_SIGNATURE = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFiYzEyMyJ9.eyJub25jZSI6IjEyMzQiLCJpc3MiOiJodHRwczovL21lLmF1dGgwLmNvbS8iLCJhdWQiOiJkYU9nbkdzUlloa3d1NjIxdmYiLCJzdWIiOiJhdXRoMHx1c2VyMTIzIn0.PkPWdoZNfXz8EB0SBPH83lNSOhyhdhdqYIgIwgY2nHozUnFOaUjVewlAXxP_3LBGibQ_ng4s5fEEOCJjaKBy04McryvOuL6nqb1dPQseeyxuv2zQitfrs-7kEtfeS3umywM-tV6guw9_W3nmIgaXOiYiF4WJM23ItbdCmvwdXLaf9-xHkQbRY_zEwEFbprFttKUXFbkPt6XjZ3zZwZbNZn64bx2PBiSJ2KMZAE3Lghmci-RXdhi7hXpmN30Tzze1ZsjvVeRRKNzShByKK9ZGZPmQ5yggJOXFy32ehjGkYwFMCqgMQomcGbcYhsd97huKHMHl3HOE5GDYjIq9o9oABC"; 39 | private static final String HS_JWT_INVALID_SIGNATURE = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJub25jZSI6IjEyMzQiLCJpc3MiOiJodHRwczovL21lLmF1dGgwLmNvbS8iLCJhdWQiOiJkYU9nbkdzUlloa3d1NjIxdmYiLCJzdWIiOiJhdXRoMHx1c2VyMTIzIn0.eTxhYFIHNii1zjxGr9QZvPcqofOd_4bHcjxGq7CQluY"; 40 | 41 | @Test 42 | public void failsWhenAlgorithmIsNotExpected() { 43 | SignatureVerifier verifier = new AlgorithmNameVerifier(); 44 | TokenValidationException e = assertThrows(TokenValidationException.class, () -> verifier.verifySignature(NONE_JWT)); 45 | assertEquals("Signature algorithm of \"none\" is not supported. Expected the ID token to be signed with \"[HS256, RS256]\".", e.getMessage()); 46 | } 47 | 48 | @Test 49 | public void failsWhenTokenCannotBeDecoded() { 50 | SignatureVerifier verifier = new SymmetricSignatureVerifier("secret"); 51 | TokenValidationException e = assertThrows(TokenValidationException.class, () -> verifier.verifySignature("boom")); 52 | assertEquals("ID token could not be decoded", e.getMessage()); 53 | } 54 | 55 | @Test 56 | public void failsWhenAlgorithmRS256IsNotExpected() { 57 | SignatureVerifier verifier = new SymmetricSignatureVerifier("secret"); 58 | TokenValidationException e = assertThrows(TokenValidationException.class, () -> verifier.verifySignature(RS_JWT)); 59 | assertEquals("Signature algorithm of \"RS256\" is not supported. Expected the ID token to be signed with \"[HS256]\".", e.getMessage()); 60 | } 61 | 62 | @Test 63 | public void failsWhenAlgorithmHS256IsNotExpected() throws Exception { 64 | SignatureVerifier verifier = new AsymmetricSignatureVerifier(getRSProvider(RS_PUBLIC_KEY)); 65 | TokenValidationException e = assertThrows(TokenValidationException.class, () -> verifier.verifySignature(HS_JWT)); 66 | assertEquals("Signature algorithm of \"HS256\" is not supported. Expected the ID token to be signed with \"[RS256]\".", e.getMessage()); 67 | } 68 | 69 | @Test 70 | public void succeedsSkippingSignatureCheckOnHS256Token() { 71 | SignatureVerifier verifier = new AlgorithmNameVerifier(); 72 | DecodedJWT decodedJWT1 = verifier.verifySignature(HS_JWT); 73 | DecodedJWT decodedJWT2 = verifier.verifySignature(HS_JWT_INVALID_SIGNATURE); 74 | 75 | assertThat(decodedJWT1, notNullValue()); 76 | assertThat(decodedJWT2, notNullValue()); 77 | } 78 | 79 | @Test 80 | public void succeedsSkippingSignatureCheckOnRS256Token() { 81 | SignatureVerifier verifier = new AlgorithmNameVerifier(); 82 | DecodedJWT decodedJWT1 = verifier.verifySignature(RS_JWT); 83 | DecodedJWT decodedJWT2 = verifier.verifySignature(RS_JWT_INVALID_SIGNATURE); 84 | 85 | assertThat(decodedJWT1, notNullValue()); 86 | assertThat(decodedJWT2, notNullValue()); 87 | } 88 | 89 | @Test 90 | public void succeedsWithValidSignatureHS256Token() { 91 | SignatureVerifier verifier = new SymmetricSignatureVerifier("secret"); 92 | DecodedJWT decodedJWT = verifier.verifySignature(HS_JWT); 93 | 94 | assertThat(decodedJWT, notNullValue()); 95 | } 96 | 97 | @Test 98 | public void succeedsAndIgnoresExpiredTokenException() { 99 | SignatureVerifier verifier = new SymmetricSignatureVerifier("secret"); 100 | DecodedJWT decodedJWT = verifier.verifySignature(EXPIRED_HS_JWT); 101 | 102 | assertThat(decodedJWT, notNullValue()); 103 | } 104 | 105 | @Test 106 | public void failsWithInvalidSignatureHS256Token() { 107 | SignatureVerifier verifier = new SymmetricSignatureVerifier("badsecret"); 108 | TokenValidationException e = assertThrows(TokenValidationException.class, () -> verifier.verifySignature(HS_JWT)); 109 | assertEquals("Invalid token signature", e.getMessage()); 110 | } 111 | 112 | @Test 113 | public void succeedsWithValidSignatureRS256Token() throws Exception { 114 | SignatureVerifier verifier = new AsymmetricSignatureVerifier(getRSProvider(RS_PUBLIC_KEY)); 115 | DecodedJWT decodedJWT = verifier.verifySignature(RS_JWT); 116 | 117 | assertThat(decodedJWT, notNullValue()); 118 | } 119 | 120 | @Test 121 | public void failsWithInvalidSignatureRS256Token() throws Exception { 122 | SignatureVerifier verifier = new AsymmetricSignatureVerifier(getRSProvider(RS_PUBLIC_KEY_BAD)); 123 | 124 | TokenValidationException e = assertThrows(TokenValidationException.class, () -> verifier.verifySignature(RS_JWT)); 125 | assertEquals("Invalid token signature", e.getMessage()); 126 | } 127 | 128 | @Test 129 | public void failsWhenErrorGettingJwk() throws Exception { 130 | JwkProvider jwkProvider = mock(JwkProvider.class); 131 | when(jwkProvider.get("abc123")).thenThrow(JwkException.class); 132 | 133 | SignatureVerifier verifier = new AsymmetricSignatureVerifier(jwkProvider); 134 | TokenValidationException e = assertThrows(TokenValidationException.class, () -> verifier.verifySignature(RS_JWT)); 135 | assertEquals("Invalid token signature", e.getMessage()); 136 | } 137 | 138 | private JwkProvider getRSProvider(String rsaPath) throws Exception { 139 | JwkProvider jwkProvider = mock(JwkProvider.class); 140 | Jwk jwk = mock(Jwk.class); 141 | when(jwkProvider.get("abc123")).thenReturn(jwk); 142 | RSAPublicKey key = readPublicKeyFromFile(rsaPath); 143 | when(jwk.getPublicKey()).thenReturn(key); 144 | return jwkProvider; 145 | } 146 | 147 | private static RSAPublicKey readPublicKeyFromFile(final String path) throws IOException { 148 | Scanner scanner = null; 149 | PemReader pemReader = null; 150 | try { 151 | scanner = new Scanner(Paths.get(path)); 152 | if (scanner.hasNextLine() && scanner.nextLine().startsWith("-----BEGIN CERTIFICATE-----")) { 153 | FileInputStream fs = new FileInputStream(path); 154 | CertificateFactory fact = CertificateFactory.getInstance("X.509"); 155 | X509Certificate cer = (X509Certificate) fact.generateCertificate(fs); 156 | PublicKey key = cer.getPublicKey(); 157 | fs.close(); 158 | return (RSAPublicKey) key; 159 | } else { 160 | pemReader = new PemReader(new FileReader(path)); 161 | byte[] keyBytes = pemReader.readPemObject().getContent(); 162 | KeyFactory kf = KeyFactory.getInstance("RSA"); 163 | EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); 164 | return (RSAPublicKey) kf.generatePublic(keySpec); 165 | } 166 | } catch (Exception e) { 167 | throw new IOException("Couldn't parse the RSA Public Key / Certificate file.", e); 168 | } finally { 169 | if (scanner != null) { 170 | scanner.close(); 171 | } 172 | if (pemReader != null) { 173 | pemReader.close(); 174 | } 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/test/java/com/auth0/TokensTest.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.hamcrest.core.Is.is; 6 | import static org.hamcrest.core.IsNull.nullValue; 7 | import static org.hamcrest.MatcherAssert.assertThat; 8 | 9 | public class TokensTest { 10 | 11 | @Test 12 | public void shouldReturnValidTokens() { 13 | Tokens tokens = new Tokens("accessToken", "idToken", "refreshToken", "bearer", 360000L); 14 | assertThat(tokens.getAccessToken(), is("accessToken")); 15 | assertThat(tokens.getIdToken(), is("idToken")); 16 | assertThat(tokens.getRefreshToken(), is("refreshToken")); 17 | assertThat(tokens.getType(), is("bearer")); 18 | assertThat(tokens.getExpiresIn(), is(360000L)); 19 | } 20 | 21 | @Test 22 | public void shouldReturnMissingTokens() { 23 | Tokens tokens = new Tokens(null, null, null, null, null); 24 | assertThat(tokens.getAccessToken(), is(nullValue())); 25 | assertThat(tokens.getIdToken(), is(nullValue())); 26 | assertThat(tokens.getRefreshToken(), is(nullValue())); 27 | assertThat(tokens.getType(), is(nullValue())); 28 | assertThat(tokens.getExpiresIn(), is(nullValue())); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/com/auth0/TransientCookieStoreTest.java: -------------------------------------------------------------------------------- 1 | package com.auth0; 2 | 3 | import org.hamcrest.beans.HasPropertyWithValue; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.mock.web.MockHttpServletRequest; 7 | import org.springframework.mock.web.MockHttpServletResponse; 8 | 9 | import javax.servlet.http.Cookie; 10 | import java.net.URLEncoder; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | import static org.hamcrest.CoreMatchers.*; 15 | import static org.hamcrest.MatcherAssert.assertThat; 16 | 17 | public class TransientCookieStoreTest { 18 | 19 | private MockHttpServletRequest request; 20 | private MockHttpServletResponse response; 21 | 22 | @BeforeEach 23 | public void setup() { 24 | request = new MockHttpServletRequest(); 25 | response = new MockHttpServletResponse(); 26 | } 27 | 28 | @Test 29 | public void shouldNotSetCookieIfStateIsNull() { 30 | TransientCookieStore.storeState(response, null, SameSite.NONE, true, false, null); 31 | 32 | List headers = response.getHeaders("Set-Cookie"); 33 | assertThat(headers.size(), is(0)); 34 | } 35 | 36 | @Test 37 | public void shouldNotSetCookieIfNonceIsNull() { 38 | TransientCookieStore.storeNonce(response, null, SameSite.NONE, true, false, null); 39 | 40 | List headers = response.getHeaders("Set-Cookie"); 41 | assertThat(headers.size(), is(0)); 42 | } 43 | 44 | @Test 45 | public void shouldHandleSpecialCharsWhenStoringState() throws Exception { 46 | String stateVal = ";state = ,va\\lu;e\""; 47 | TransientCookieStore.storeState(response, stateVal, SameSite.NONE, true, false, null); 48 | 49 | List headers = response.getHeaders("Set-Cookie"); 50 | assertThat(headers.size(), is(2)); 51 | 52 | String expectedEncodedState = URLEncoder.encode(stateVal, "UTF-8"); 53 | assertThat(headers, hasItem( 54 | String.format("com.auth0.state=%s; HttpOnly; Max-Age=600; SameSite=None; Secure", expectedEncodedState))); 55 | assertThat(headers, hasItem( 56 | String.format("_com.auth0.state=%s; HttpOnly; Max-Age=600", expectedEncodedState))); 57 | } 58 | 59 | @Test 60 | public void shouldSetStateSameSiteCookieAndFallbackCookie() { 61 | TransientCookieStore.storeState(response, "123456", SameSite.NONE, true, false, null); 62 | 63 | List headers = response.getHeaders("Set-Cookie"); 64 | assertThat(headers.size(), is(2)); 65 | 66 | assertThat(headers, hasItem("com.auth0.state=123456; HttpOnly; Max-Age=600; SameSite=None; Secure")); 67 | assertThat(headers, hasItem("_com.auth0.state=123456; HttpOnly; Max-Age=600")); 68 | } 69 | 70 | @Test 71 | public void shouldSetStateSameSiteCookieAndNoFallbackCookie() { 72 | TransientCookieStore.storeState(response, "123456", SameSite.NONE, false, false, null); 73 | 74 | List headers = response.getHeaders("Set-Cookie"); 75 | assertThat(headers.size(), is(1)); 76 | 77 | assertThat(headers, hasItem("com.auth0.state=123456; HttpOnly; Max-Age=600; SameSite=None; Secure")); 78 | } 79 | 80 | @Test 81 | public void shouldSetSecureCookieWhenSameSiteLaxAndConfigured() { 82 | TransientCookieStore.storeState(response, "123456", SameSite.LAX, true, true, null); 83 | 84 | List headers = response.getHeaders("Set-Cookie"); 85 | assertThat(headers.size(), is(1)); 86 | 87 | assertThat(headers, hasItem("com.auth0.state=123456; HttpOnly; Max-Age=600; SameSite=Lax; Secure")); 88 | } 89 | 90 | @Test 91 | public void shouldSetSecureFallbackCookieWhenSameSiteNoneAndConfigured() { 92 | TransientCookieStore.storeState(response, "123456", SameSite.NONE, true, true, null); 93 | 94 | List headers = response.getHeaders("Set-Cookie"); 95 | assertThat(headers.size(), is(2)); 96 | 97 | assertThat(headers, hasItem("com.auth0.state=123456; HttpOnly; Max-Age=600; SameSite=None; Secure")); 98 | assertThat(headers, hasItem("_com.auth0.state=123456; HttpOnly; Max-Age=600; Secure")); 99 | } 100 | 101 | @Test 102 | public void shouldNotSetSecureCookieWhenSameSiteLaxAndConfigured() { 103 | TransientCookieStore.storeState(response, "123456", SameSite.LAX, true, false, null); 104 | 105 | List headers = response.getHeaders("Set-Cookie"); 106 | assertThat(headers.size(), is(1)); 107 | 108 | assertThat(headers, hasItem("com.auth0.state=123456; HttpOnly; Max-Age=600; SameSite=Lax")); 109 | } 110 | 111 | @Test 112 | public void shouldSetNonceSameSiteCookieAndFallbackCookie() { 113 | TransientCookieStore.storeNonce(response, "123456", SameSite.NONE, true, false, null); 114 | 115 | List headers = response.getHeaders("Set-Cookie"); 116 | assertThat(headers.size(), is(2)); 117 | 118 | assertThat(headers, hasItem("com.auth0.nonce=123456; HttpOnly; Max-Age=600; SameSite=None; Secure")); 119 | assertThat(headers, hasItem("_com.auth0.nonce=123456; HttpOnly; Max-Age=600")); 120 | } 121 | 122 | @Test 123 | public void shouldSetNonceSameSiteCookieAndNoFallbackCookie() { 124 | TransientCookieStore.storeNonce(response, "123456", SameSite.NONE, false, false, null); 125 | 126 | List headers = response.getHeaders("Set-Cookie"); 127 | assertThat(headers.size(), is(1)); 128 | 129 | assertThat(headers, hasItem("com.auth0.nonce=123456; HttpOnly; Max-Age=600; SameSite=None; Secure")); 130 | } 131 | 132 | @Test 133 | public void shouldRemoveStateSameSiteCookieAndFallbackCookie() { 134 | Cookie cookie1 = new Cookie("com.auth0.state", "123456"); 135 | Cookie cookie2 = new Cookie("_com.auth0.state", "123456"); 136 | 137 | request.setCookies(cookie1, cookie2); 138 | 139 | String state = TransientCookieStore.getState(request, response); 140 | assertThat(state, is("123456")); 141 | 142 | Cookie[] cookies = response.getCookies(); 143 | assertThat(cookies, is(notNullValue())); 144 | 145 | List cookieList = Arrays.asList(cookies); 146 | assertThat(cookieList.size(), is(2)); 147 | 148 | assertThat(Arrays.asList(cookies), everyItem(HasPropertyWithValue.hasProperty("value", is("")))); 149 | assertThat(Arrays.asList(cookies), everyItem(HasPropertyWithValue.hasProperty("maxAge", is(0)))); 150 | } 151 | 152 | @Test 153 | public void shouldRemoveStateSameSiteCookie() { 154 | Cookie cookie1 = new Cookie("com.auth0.state", "123456"); 155 | 156 | request.setCookies(cookie1); 157 | 158 | String state = TransientCookieStore.getState(request, response); 159 | assertThat(state, is("123456")); 160 | 161 | Cookie[] cookies = response.getCookies(); 162 | assertThat(cookies, is(notNullValue())); 163 | 164 | List cookieList = Arrays.asList(cookies); 165 | assertThat(cookieList.size(), is(1)); 166 | assertThat(Arrays.asList(cookies), everyItem(HasPropertyWithValue.hasProperty("value", is("")))); 167 | assertThat(Arrays.asList(cookies), everyItem(HasPropertyWithValue.hasProperty("maxAge", is(0)))); 168 | } 169 | 170 | @Test 171 | public void shouldRemoveStateFallbackCookie() { 172 | Cookie cookie1 = new Cookie("_com.auth0.state", "123456"); 173 | 174 | request.setCookies(cookie1); 175 | 176 | String state = TransientCookieStore.getState(request, response); 177 | assertThat(state, is("123456")); 178 | 179 | Cookie[] cookies = response.getCookies(); 180 | assertThat(cookies, is(notNullValue())); 181 | 182 | List cookieList = Arrays.asList(cookies); 183 | assertThat(cookieList.size(), is(1)); 184 | assertThat(Arrays.asList(cookies), everyItem(HasPropertyWithValue.hasProperty("value", is("")))); 185 | assertThat(Arrays.asList(cookies), everyItem(HasPropertyWithValue.hasProperty("maxAge", is(0)))); 186 | } 187 | 188 | @Test 189 | public void shouldRemoveNonceSameSiteCookieAndFallbackCookie() { 190 | Cookie cookie1 = new Cookie("com.auth0.nonce", "123456"); 191 | Cookie cookie2 = new Cookie("_com.auth0.nonce", "123456"); 192 | 193 | request.setCookies(cookie1, cookie2); 194 | 195 | String state = TransientCookieStore.getNonce(request, response); 196 | assertThat(state, is("123456")); 197 | 198 | Cookie[] cookies = response.getCookies(); 199 | assertThat(cookies, is(notNullValue())); 200 | 201 | List cookieList = Arrays.asList(cookies); 202 | assertThat(cookieList.size(), is(2)); 203 | assertThat(Arrays.asList(cookies), everyItem(HasPropertyWithValue.hasProperty("value", is("")))); 204 | assertThat(Arrays.asList(cookies), everyItem(HasPropertyWithValue.hasProperty("maxAge", is(0)))); 205 | } 206 | 207 | @Test 208 | public void shouldRemoveNonceSameSiteCookie() { 209 | Cookie cookie1 = new Cookie("com.auth0.nonce", "123456"); 210 | 211 | request.setCookies(cookie1); 212 | 213 | String state = TransientCookieStore.getNonce(request, response); 214 | assertThat(state, is("123456")); 215 | 216 | Cookie[] cookies = response.getCookies(); 217 | assertThat(cookies, is(notNullValue())); 218 | 219 | List cookieList = Arrays.asList(cookies); 220 | assertThat(cookieList.size(), is(1)); 221 | assertThat(Arrays.asList(cookies), everyItem(HasPropertyWithValue.hasProperty("value", is("")))); 222 | assertThat(Arrays.asList(cookies), everyItem(HasPropertyWithValue.hasProperty("maxAge", is(0)))); 223 | } 224 | 225 | @Test 226 | public void shouldRemoveNonceFallbackCookie() { 227 | Cookie cookie1 = new Cookie("_com.auth0.nonce", "123456"); 228 | 229 | request.setCookies(cookie1); 230 | 231 | String state = TransientCookieStore.getNonce(request, response); 232 | assertThat(state, is("123456")); 233 | 234 | Cookie[] cookies = response.getCookies(); 235 | assertThat(cookies, is(notNullValue())); 236 | 237 | List cookieList = Arrays.asList(cookies); 238 | assertThat(cookieList.size(), is(1)); 239 | assertThat(Arrays.asList(cookies), everyItem(HasPropertyWithValue.hasProperty("value", is("")))); 240 | assertThat(Arrays.asList(cookies), everyItem(HasPropertyWithValue.hasProperty("maxAge", is(0)))); 241 | } 242 | 243 | @Test 244 | public void shouldReturnEmptyStateWhenNoCookies() { 245 | String state = TransientCookieStore.getState(request, response); 246 | assertThat(state, is(nullValue())); 247 | } 248 | 249 | @Test 250 | public void shouldReturnEmptyNonceWhenNoCookies() { 251 | String nonce = TransientCookieStore.getNonce(request, response); 252 | assertThat(nonce, is(nullValue())); 253 | } 254 | 255 | @Test 256 | public void shouldReturnEmptyWhenNoStateCookie() { 257 | Cookie cookie1 = new Cookie("someCookie", "123456"); 258 | request.setCookies(cookie1); 259 | 260 | String state = TransientCookieStore.getState(request, response); 261 | assertThat(state, is(nullValue())); 262 | } 263 | 264 | @Test 265 | public void shouldReturnEmptyWhenNoNonceCookie() { 266 | Cookie cookie1 = new Cookie("someCookie", "123456"); 267 | request.setCookies(cookie1); 268 | 269 | String nonce = TransientCookieStore.getNonce(request, response); 270 | assertThat(nonce, is(nullValue())); 271 | assertThat(nonce, is(nullValue())); 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/test/resources/bad-public.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzELuTuakDmN69DZoZfEO 3 | tYLLHnwcAJGlk9qKU3F+VWyadLuOVZ9cApJpZkpi+zPFOTnmVK9NyVgN9mA4lyDj 4 | G4SDB8NdzMuIidsoSWUqiu05z8bqAFO9SH284hDJ+eVuT6a0nru6mFczr64AZXNz 5 | YyL1nyjih0bujcZPKLvNp0g7NvCmxgpGOth9IF5goMsLFgA3SuCLpMzRB4+czIqR 6 | E6XRyfojo0xX1dNzYxohM7ai1WD3Maq3GwcKV+r1H3Jp8p0yGya5hc1XPDhkidD5 7 | 46mVrdja13IwhVoy0QloeKhEd6cV1Gbd9BWbU4Q13BXb8JJ+AQjXr0cgn0l3wYvb 8 | hQIDAQAc 9 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /src/test/resources/certificate.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDtTCCAp2gAwIBAgIJANUC45VpA5H0MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV 3 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX 4 | aWRnaXRzIFB0eSBMdGQwHhcNMTcwMjEzMTg1MzA3WhcNMTcwMzE1MTg1MzA3WjBF 5 | MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 6 | ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 7 | CgKCAQEArP9kZ5hXOYigPH+cNIbghQiYoChDZ59DlYSTkwRHNR7fi0UAL72NwxrL 8 | Y1vSuf2PSEe+NEy/uxc/V39NGMBr7zAaIV/kZw1Dm6+dTfLNKNsS7IebhAq2aVfC 9 | RBsDmuo7mtaOhRzFt4IQH+ga9lJPLQyczHN7F5PEd3elJ1MMjlLs0wkyT9E3pIU0 10 | 2gJ1czVFVxwD06Vth/DvP9sWzqbqUqDrS2WACwmH/OQp8LJ9thQgprkTeNbY70vQ 11 | 6k1KJbkVTS/DqZyOCzfUM8EGpPlktz4VHsvE9RvY6Eg+erAb1exMQZtOwuav5V6C 12 | Qp4lqejCmP/fVWg8/yJSMlviBCrk1QIDAQABo4GnMIGkMB0GA1UdDgQWBBRpDjdw 13 | 3aSj++hUp8men4TPoYhQFjB1BgNVHSMEbjBsgBRpDjdw3aSj++hUp8men4TPoYhQ 14 | FqFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV 15 | BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJANUC45VpA5H0MAwGA1UdEwQF 16 | MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAGLAmY4t6A9HMTr/gV/hXlxUQww5pF93 17 | v1gFYoOLLz6MvnYnjgAcflYaYx327nsKbgMRIlCM8q/Y52kx5T8wvR2nE+fBWkSo 18 | +NWG7OKSkmiEGlwYUqc+8seJBux2GxU9R9gpS8qdhz+5w0R4o/ZGJrP7W2EAzxO6 19 | 99dm/FwuQzcfTgLGtAsFq+DCoNPm+7aFUjhnoS5LGpFzIwfGrtmiXlAypf/5JQH6 20 | D2hYzMbajRq5LplO6s9HJAWSWtNEy0grK5BPZlP4EZ/VMzMPNJLEeXEjBSdLecjd 21 | CkT6gfMJoXiSjxpDDKY8U4FcZLEqdhibjLCmB2Jf5vMdwgIYyXPthQk= 22 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /src/test/resources/private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAzELuTuakDmN69DZoZfEOtYLLHnwcAJGlk9qKU3F+VWyadLuO 3 | VZ9cApJpZkpi+zPFOTnmVK9NyVgN9mA4lyDjG4SDB8NdzMuIidsoSWUqiu05z8bq 4 | AFO9SH284hDJ+eVuT6a0nru6mFczr64AZXNzYyL1nyjih0bujcZPKLvNp0g7NvCm 5 | xgpGOth9IF5goMsLFgA3SuCLpMzRB4+czIqRE6XRyfojo0xX1dNzYxohM7ai1WD3 6 | Maq3GwcKV+r1H3Jp8p0yGya5hc1XPDhkidD546mVrdja13IwhVoy0QloeKhEd6cV 7 | 1Gbd9BWbU4Q13BXb8JJ+AQjXr0cgn0l3wYvbhQIDAQABAoIBADW57SDHvaMjO3X8 8 | 0ZzuuOW5T1/nWR41MQ4gz31/PrGNbVsC8j9HOVZb2GlVa4+TYG7a8eCYc6dgB9wk 9 | Fv55YgMc6bIjCwsRBgrsoD4d5ADCwRALzATmAcWNfR8hN18Jn+hHev69dikX1rqA 10 | 1A3bPiB3U4/Xmy3l1F1qKewW6dLzzfTiemS6N3lsoV7IqiVP9F5v3pYz9DV4w0Rv 11 | pZz6H3X7IVRXsVHo7rDLgNG1l2yPzZCKJZp1L95Fet3ZPV/St5PS9QojG6E766i6 12 | oyT+9u6328ByjxIjtLD9LjUzbx5sas5+ucj6PxrLrCAh7bBOQFG2f3cuGN/vUs2p 13 | sMXf1gECgYEA+8lPY3o4S0inziz/erAK+pkq8FR3TKct6zdDtZKPyj1+I5bYJgAs 14 | i+erbc4EcNu0fhtE/ecKVHL8DViHeJgeH8FUTMhLXabfc7kzq5FY1tlj29VKVwbI 15 | EwfaBJZOBJfv2KJMZFi91rxarlKL2tMV2IzEIdmri3YLMdD5tLuoLkUCgYEAz64E 16 | W98So1xXyfVUm4mnW3L/jrD9bB3F+W3DEPLCMC/265nqZNtgLmVYzEUApKOUzyak 17 | 3gvEBdEKhp26OjBNMzH9WeqwvX7hV15ze4XgxwdR9MURVDFVGKN2Jz5rmzbka8pI 18 | Tb+tTdVJa+STC5vn+aXwnk30H27IlvaFP5G3bEECgYAr48uS65skhGW76twd5tID 19 | HO//NLoXAo1ekyOaYxHtjwqN6Z2EAkPBn4Cig2cCxUmRMe+00rFaRzDooqX6v4mA 20 | v5KST9fTW2NYKNB4970ktoBRsbxjryrxJk06v3iaFGIawS/PzihJZkvoVztJycHl 21 | HaSSwf7FMK6C4Iqmlo+agQKBgQClBsWF73vS/fTwkZb6xWaMX5Q9YdzuxbXkX1kL 22 | JawzOFoAm1zLahtCotvt7cL2ENqVxUJrA3Rvns27bKhnxqwBy25jvf9VhPYxQ+eG 23 | NzsHTITOeK4tdcoF+xZPVuWRAAyArsvHpFFlMN/X8Vj7d5bdJQRP9ufO9mxEsX3u 24 | pq7XQQKBgQDsyfwuS8efdwctHletPWW2CEPqsGLkX/sU7/dDg9n6BvAojTKoyh5A 25 | VoTO7sTOFq8yfKN3jk6BN04u1tbR1TQKvw0I5WyaZQ7DMnZlke1FjP5wDpnFpoOW 26 | qkmlM9OFj/ucPR3QfV9cI/tr3wkrz4R7dYTamPUg/Kbmp4je9mzz5A== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /src/test/resources/public.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzELuTuakDmN69DZoZfEO 3 | tYLLHnwcAJGlk9qKU3F+VWyadLuOVZ9cApJpZkpi+zPFOTnmVK9NyVgN9mA4lyDj 4 | G4SDB8NdzMuIidsoSWUqiu05z8bqAFO9SH284hDJ+eVuT6a0nru6mFczr64AZXNz 5 | YyL1nyjih0bujcZPKLvNp0g7NvCmxgpGOth9IF5goMsLFgA3SuCLpMzRB4+czIqR 6 | E6XRyfojo0xX1dNzYxohM7ai1WD3Maq3GwcKV+r1H3Jp8p0yGya5hc1XPDhkidD5 7 | 46mVrdja13IwhVoy0QloeKhEd6cV1Gbd9BWbU4Q13BXb8JJ+AQjXr0cgn0l3wYvb 8 | hQIDAQAB 9 | -----END PUBLIC KEY----- 10 | --------------------------------------------------------------------------------