├── .autover └── autover.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.yml │ ├── config.yml │ ├── documentation.yml │ └── feature-request.yml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── aws-ci.yml │ ├── change-file-in-pr.yml │ ├── closed-issue-message.yml │ ├── create-release-pr.yml │ ├── handle-stale-discussions.yml │ ├── issue-regression-labeler.yml │ ├── semgrep-analysis.yml │ ├── stale_issues.yml │ └── sync-main-dev.yml ├── .gitignore ├── Amazon.Extensions.CognitoAuthentication.sln ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── buildtools └── ci.buildspec.yml ├── icon.png ├── logo.png ├── public.snk ├── src └── Amazon.Extensions.CognitoAuthentication │ ├── Amazon.Extensions.CognitoAuthentication.csproj │ ├── CognitoAuthenticationClasses.cs │ ├── CognitoDevice.cs │ ├── CognitoUser.cs │ ├── CognitoUserAuthentication.cs │ ├── CognitoUserPool.cs │ ├── CognitoUserPoolClientConfiguration.cs │ ├── CognitoUserSession.cs │ ├── Properties │ └── AssemblyInfo.cs │ └── Util │ ├── AuthenticationHelper.cs │ ├── BigIntegerExtensions.cs │ ├── CognitoAuthHelper.cs │ ├── CognitoConstants.cs │ ├── CognitoDeviceHelper.cs │ └── HkdfSha256.cs └── test ├── Amazon.Extensions.CognitoAuthentication.IntegrationTests ├── Amazon.Extensions.CognitoAuthentication.IntegrationTests.csproj ├── AuthenticationConfirmUserTests.cs ├── AuthenticationCreateUserTests.cs ├── AuthenticationSignUpUserTests.cs ├── BaseAuthenticationTestClass.cs ├── CognitoAWSCredentialsTests.cs ├── MfaAuthenticationTests.cs ├── SessionTests.cs └── _coreclr │ └── BaseAuthenticationTestClass.cs └── Amazon.Extensions.CognitoAuthentication.UnitTests ├── Amazon.Extensions.CognitoAuthentication.UnitTests.csproj ├── AuthenticationCryptoTests.cs ├── BigIntegerExtensionsTests.cs └── SecurityComplianceTests.cs /.autover/autover.json: -------------------------------------------------------------------------------- 1 | { 2 | "Projects": [ 3 | { 4 | "Name": "Amazon.Extensions.CognitoAuthentication", 5 | "Path": "src/Amazon.Extensions.CognitoAuthentication/Amazon.Extensions.CognitoAuthentication.csproj" 6 | } 7 | ], 8 | "UseCommitsForChangelog": false, 9 | "UseSameVersionForAllProjects": false, 10 | "DefaultIncrementType": "Patch", 11 | "ChangeFilesDetermineIncrementType": true 12 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "🐛 Bug Report" 3 | description: Report a bug 4 | title: "(short issue description)" 5 | labels: [bug, needs-triage] 6 | assignees: [] 7 | body: 8 | - type: textarea 9 | id: description 10 | attributes: 11 | label: Describe the bug 12 | description: What is the problem? A clear and concise description of the bug. 13 | validations: 14 | required: true 15 | - type: checkboxes 16 | id: regression 17 | attributes: 18 | label: Regression Issue 19 | description: What is a regression? If it worked in a previous version but doesn't in the latest version, it's considered a regression. In this case, please provide specific version number in the report. 20 | options: 21 | - label: Select this option if this issue appears to be a regression. 22 | required: false 23 | - type: textarea 24 | id: expected 25 | attributes: 26 | label: Expected Behavior 27 | description: | 28 | What did you expect to happen? 29 | validations: 30 | required: true 31 | - type: textarea 32 | id: current 33 | attributes: 34 | label: Current Behavior 35 | description: | 36 | What actually happened? 37 | 38 | Please include full errors, uncaught exceptions, stack traces, and relevant logs. 39 | If service responses are relevant, please include wire logs. 40 | validations: 41 | required: true 42 | - type: textarea 43 | id: reproduction 44 | attributes: 45 | label: Reproduction Steps 46 | description: | 47 | Provide a self-contained, concise snippet of code that can be used to reproduce the issue. 48 | For more complex issues provide a repo with the smallest sample that reproduces the bug. 49 | 50 | Avoid including business logic or unrelated code, it makes diagnosis more difficult. 51 | The code sample should be an SSCCE. See http://sscce.org/ for details. In short, please provide a code sample that we can copy/paste, run and reproduce. 52 | validations: 53 | required: true 54 | - type: textarea 55 | id: solution 56 | attributes: 57 | label: Possible Solution 58 | description: | 59 | Suggest a fix/reason for the bug 60 | validations: 61 | required: false 62 | - type: textarea 63 | id: context 64 | attributes: 65 | label: Additional Information/Context 66 | description: | 67 | Anything else that might be relevant for troubleshooting this bug. Providing context helps us come up with a solution that is most useful in the real world. 68 | validations: 69 | required: false 70 | 71 | - type: textarea 72 | id: dotnet-sdk-version 73 | attributes: 74 | label: AWS .NET SDK and/or Package version used 75 | description: NuGet Packages used 76 | placeholder: AWSSDK.S3 3.7.8.13 77 | validations: 78 | required: true 79 | 80 | - type: input 81 | id: platform-used 82 | attributes: 83 | label: Targeted .NET Platform 84 | description: "Example: .NET Framework 4.7, .NET Core 3.1, .NET 6, etc." 85 | placeholder: .NET Framework 4.7, .NET Core 3.1, .NET 6, etc. 86 | validations: 87 | required: true 88 | 89 | - type: input 90 | id: operating-system 91 | attributes: 92 | label: Operating System and version 93 | description: "Example: Windows 10, OSX Mojave, Ubuntu, AmazonLinux, etc." 94 | placeholder: Windows 10, OSX Mojave, Ubuntu, AmazonLinux, etc. 95 | validations: 96 | required: true 97 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | blank_issues_enabled: false 3 | contact_links: 4 | - name: 💬 General Question 5 | url: https://github.com/aws/aws-sdk-net-extensions-cognito/discussions/categories/q-a 6 | about: Please ask and answer questions as a discussion thread -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "📕 Documentation Issue" 3 | description: Report an issue in the API Reference documentation or Developer Guide 4 | title: "(short issue description)" 5 | labels: [documentation, needs-triage] 6 | assignees: [] 7 | body: 8 | - type: textarea 9 | id: description 10 | attributes: 11 | label: Describe the issue 12 | description: A clear and concise description of the issue. 13 | validations: 14 | required: true 15 | 16 | - type: textarea 17 | id: links 18 | attributes: 19 | label: Links 20 | description: | 21 | Include links to affected documentation page(s). 22 | validations: 23 | required: true 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚀 Feature Request 3 | description: Suggest an idea for this project 4 | title: "(short issue description)" 5 | labels: [feature-request, needs-triage] 6 | assignees: [] 7 | body: 8 | - type: textarea 9 | id: description 10 | attributes: 11 | label: Describe the feature 12 | description: A clear and concise description of the feature you are proposing. 13 | validations: 14 | required: true 15 | - type: textarea 16 | id: use-case 17 | attributes: 18 | label: Use Case 19 | description: | 20 | Why do you need this feature? For example: "I'm always frustrated when..." 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: solution 25 | attributes: 26 | label: Proposed Solution 27 | description: | 28 | Suggest how to implement the addition or change. Please include prototype/workaround/sketch/reference implementation. 29 | validations: 30 | required: false 31 | - type: textarea 32 | id: other 33 | attributes: 34 | label: Other Information 35 | description: | 36 | Any alternative solutions or features you considered, a more detailed explanation, stack traces, related issues, links for context, etc. 37 | validations: 38 | required: false 39 | - type: checkboxes 40 | id: ack 41 | attributes: 42 | label: Acknowledgements 43 | options: 44 | - label: I may be able to implement this feature request 45 | required: false 46 | - label: This feature might incur a breaking change 47 | required: false 48 | 49 | - type: textarea 50 | id: dotnet-sdk-version 51 | attributes: 52 | label: AWS .NET SDK and/or Package version used 53 | description: NuGet Packages used 54 | placeholder: AWSSDK.S3 3.7.8.13 55 | validations: 56 | required: true 57 | 58 | - type: input 59 | id: platform-used 60 | attributes: 61 | label: Targeted .NET Platform 62 | description: "Example: .NET Framework 4.7, .NET Core 3.1, .NET 6, etc." 63 | placeholder: .NET Framework 4.7, .NET Core 3.1, .NET 6, etc. 64 | validations: 65 | required: true 66 | 67 | - type: input 68 | id: operating-system 69 | attributes: 70 | label: Operating System and version 71 | description: "Example: Windows 10, OSX Mojave, Ubuntu, AmazonLinux, etc." 72 | placeholder: Windows 10, OSX Mojave, Ubuntu, AmazonLinux, etc. 73 | validations: 74 | required: true 75 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Set update schedule for GitHub Actions 2 | 3 | version: 2 4 | updates: 5 | 6 | - package-ecosystem: "github-actions" 7 | directory: "/" 8 | schedule: 9 | # Check for updates to GitHub Actions every week 10 | interval: "weekly" 11 | labels: 12 | - "Release Not Needed" 13 | target-branch: "dev" -------------------------------------------------------------------------------- /.github/workflows/aws-ci.yml: -------------------------------------------------------------------------------- 1 | name: AWS CI 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | branches: 7 | - main 8 | - dev 9 | - 'feature/**' 10 | 11 | permissions: 12 | id-token: write 13 | 14 | jobs: 15 | run-ci: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Configure AWS Credentials 19 | uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 #v4 20 | with: 21 | role-to-assume: ${{ secrets.CI_MAIN_TESTING_ACCOUNT_ROLE_ARN }} 22 | role-duration-seconds: 7200 23 | aws-region: us-west-2 24 | - name: Invoke Load Balancer Lambda 25 | id: lambda 26 | shell: pwsh 27 | run: | 28 | aws lambda invoke response.json --function-name "${{ secrets.CI_TESTING_LOAD_BALANCER_LAMBDA_NAME }}" --cli-binary-format raw-in-base64-out --payload '{"Roles": "${{ secrets.CI_TEST_RUNNER_ACCOUNT_ROLES }}", "ProjectName": "${{ secrets.CI_TESTING_CODE_BUILD_PROJECT_NAME }}", "Branch": "${{ github.sha }}"}' 29 | $roleArn=$(cat ./response.json) 30 | "roleArn=$($roleArn -replace '"', '')" >> $env:GITHUB_OUTPUT 31 | - name: Configure Test Runner Credentials 32 | uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 #v4 33 | with: 34 | role-to-assume: ${{ steps.lambda.outputs.roleArn }} 35 | role-duration-seconds: 7200 36 | aws-region: us-west-2 37 | - name: Run Tests on AWS 38 | id: codebuild 39 | uses: aws-actions/aws-codebuild-run-build@4d15a47425739ac2296ba5e7eee3bdd4bfbdd767 #v1.0.18 40 | with: 41 | project-name: ${{ secrets.CI_TESTING_CODE_BUILD_PROJECT_NAME }} 42 | - name: CodeBuild Link 43 | shell: pwsh 44 | run: | 45 | $buildId = "${{ steps.codebuild.outputs.aws-build-id }}" 46 | echo $buildId -------------------------------------------------------------------------------- /.github/workflows/change-file-in-pr.yml: -------------------------------------------------------------------------------- 1 | name: Change File Included in PR 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, labeled] 6 | 7 | jobs: 8 | check-files-in-directory: 9 | if: ${{ !contains(github.event.pull_request.labels.*.name, 'Release Not Needed') && !contains(github.event.pull_request.labels.*.name, 'Release PR') }} 10 | name: Change File Included in PR 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout PR code 15 | uses: actions/checkout@v3 16 | 17 | - name: Get List of Changed Files 18 | id: changed-files 19 | uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c #v45 20 | 21 | - name: Check for Change File(s) in .autover/changes/ 22 | run: | 23 | DIRECTORY=".autover/changes/" 24 | if echo "${{ steps.changed-files.outputs.all_changed_files }}" | grep -q "$DIRECTORY"; then 25 | echo "✅ One or more change files in '$DIRECTORY' are included in this PR." 26 | else 27 | echo "❌ No change files in '$DIRECTORY' are included in this PR." 28 | echo "Refer to the 'Adding a change file to your contribution branch' section of https://github.com/aws/aws-sdk-net-extensions-cognito/blob/master/CONTRIBUTING.md" 29 | exit 1 30 | fi 31 | -------------------------------------------------------------------------------- /.github/workflows/closed-issue-message.yml: -------------------------------------------------------------------------------- 1 | name: Closed Issue Message 2 | on: 3 | issues: 4 | types: [closed] 5 | jobs: 6 | auto_comment: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: aws-actions/closed-issue-message@v1 10 | with: 11 | # These inputs are both required 12 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 13 | message: | 14 | ### ⚠️COMMENT VISIBILITY WARNING⚠️ 15 | Comments on closed issues are hard for our team to see. 16 | If you need more assistance, please either tag a team member or open a new issue that references this one. 17 | If you wish to keep having a conversation with other community members under this issue feel free to do so. 18 | -------------------------------------------------------------------------------- /.github/workflows/create-release-pr.yml: -------------------------------------------------------------------------------- 1 | # This GitHub Workflow will create a new release branch that contains the updated C# project versions and changelog. 2 | # The workflow will also create a PR that targets `dev` from the release branch. 3 | name: Create Release PR 4 | 5 | # This workflow is manually triggered when in preparation for a release. The workflow should be dispatched from the `dev` branch. 6 | on: 7 | workflow_dispatch: 8 | inputs: 9 | OVERRIDE_VERSION: 10 | description: "Override Version" 11 | type: string 12 | required: false 13 | 14 | permissions: 15 | id-token: write 16 | 17 | jobs: 18 | release-pr: 19 | name: Release PR 20 | runs-on: ubuntu-latest 21 | 22 | env: 23 | INPUT_OVERRIDE_VERSION: ${{ github.event.inputs.OVERRIDE_VERSION }} 24 | 25 | steps: 26 | # Assume an AWS Role that provides access to the Access Token 27 | - name: Configure AWS Credentials 28 | uses: aws-actions/configure-aws-credentials@8c3f20df09ac63af7b3ae3d7c91f105f857d8497 #v4 29 | with: 30 | role-to-assume: ${{ secrets.RELEASE_WORKFLOW_ACCESS_TOKEN_ROLE_ARN }} 31 | aws-region: us-west-2 32 | # Retrieve the Access Token from Secrets Manager 33 | - name: Retrieve secret from AWS Secrets Manager 34 | uses: aws-actions/aws-secretsmanager-get-secrets@fbd65ea98e018858715f591f03b251f02b2316cb #v2.0.8 35 | with: 36 | secret-ids: | 37 | AWS_SECRET, ${{ secrets.RELEASE_WORKFLOW_ACCESS_TOKEN_NAME }} 38 | parse-json-secrets: true 39 | # Checkout a full clone of the repo 40 | - name: Checkout 41 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 42 | with: 43 | fetch-depth: '0' 44 | token: ${{ env.AWS_SECRET_TOKEN }} 45 | # Install .NET8 which is needed for AutoVer 46 | - name: Setup .NET 8.0 47 | uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 #v4.3.1 48 | with: 49 | dotnet-version: 8.0.x 50 | # Install AutoVer to automate versioning and changelog creation 51 | - name: Install AutoVer 52 | run: dotnet tool install --global AutoVer --version 0.0.25 53 | # Set up a git user to be able to run git commands later on 54 | - name: Setup Git User 55 | run: | 56 | git config --global user.email "github-aws-sdk-dotnet-automation@amazon.com" 57 | git config --global user.name "aws-sdk-dotnet-automation" 58 | # Create the release branch which will contain the version changes and updated changelog 59 | - name: Create Release Branch 60 | id: create-release-branch 61 | run: | 62 | branch=releases/next-release 63 | git checkout -b $branch 64 | echo "BRANCH=$branch" >> $GITHUB_OUTPUT 65 | # Update the version of projects based on the change files 66 | - name: Increment Version 67 | run: autover version 68 | if: env.INPUT_OVERRIDE_VERSION == '' 69 | # Update the version of projects based on the override version 70 | - name: Increment Version 71 | run: autover version --use-version "$INPUT_OVERRIDE_VERSION" 72 | if: env.INPUT_OVERRIDE_VERSION != '' 73 | # Update the changelog based on the change files 74 | - name: Update Changelog 75 | run: autover changelog 76 | # Push the release branch up as well as the created tag 77 | - name: Push Changes 78 | run: | 79 | branch=${{ steps.create-release-branch.outputs.BRANCH }} 80 | git push origin $branch 81 | git push origin $branch --tags 82 | # Get the release name that will be used to create a PR 83 | - name: Read Release Name 84 | id: read-release-name 85 | run: | 86 | version=$(autover changelog --release-name) 87 | echo "VERSION=$version" >> $GITHUB_OUTPUT 88 | # Get the changelog that will be used to create a PR 89 | - name: Read Changelog 90 | id: read-changelog 91 | run: | 92 | changelog=$(autover changelog --output-to-console) 93 | echo "CHANGELOG<> "$GITHUB_OUTPUT" 94 | # Create the Release PR and label it 95 | - name: Create Pull Request 96 | env: 97 | GITHUB_TOKEN: ${{ env.AWS_SECRET_TOKEN }} 98 | run: | 99 | pr_url="$(gh pr create --title "${{ steps.read-release-name.outputs.VERSION }}" --body "${{ steps.read-changelog.outputs.CHANGELOG }}" --base dev --head ${{ steps.create-release-branch.outputs.BRANCH }})" 100 | gh label create "Release PR" --description "A Release PR that includes versioning and changelog changes" -c "#FF0000" -f 101 | gh pr edit $pr_url --add-label "Release PR" -------------------------------------------------------------------------------- /.github/workflows/handle-stale-discussions.yml: -------------------------------------------------------------------------------- 1 | name: HandleStaleDiscussions 2 | on: 3 | schedule: 4 | - cron: '0 */4 * * *' 5 | discussion_comment: 6 | types: [created] 7 | 8 | jobs: 9 | handle-stale-discussions: 10 | name: Handle stale discussions 11 | runs-on: ubuntu-latest 12 | permissions: 13 | discussions: write 14 | steps: 15 | - name: Stale discussions action 16 | uses: aws-github-ops/handle-stale-discussions@v1.6.0 17 | env: 18 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 19 | -------------------------------------------------------------------------------- /.github/workflows/issue-regression-labeler.yml: -------------------------------------------------------------------------------- 1 | # Apply potential regression label on issues 2 | name: issue-regression-label 3 | on: 4 | issues: 5 | types: [opened, edited] 6 | jobs: 7 | add-regression-label: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | steps: 12 | - name: Fetch template body 13 | id: check_regression 14 | uses: actions/github-script@v7 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | TEMPLATE_BODY: ${{ github.event.issue.body }} 18 | with: 19 | script: | 20 | const regressionPattern = /\[x\] Select this option if this issue appears to be a regression\./i; 21 | const template = `${process.env.TEMPLATE_BODY}` 22 | const match = regressionPattern.test(template); 23 | core.setOutput('is_regression', match); 24 | - name: Manage regression label 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | run: | 28 | if [ "${{ steps.check_regression.outputs.is_regression }}" == "true" ]; then 29 | gh issue edit ${{ github.event.issue.number }} --add-label "potential-regression" -R ${{ github.repository }} 30 | else 31 | gh issue edit ${{ github.event.issue.number }} --remove-label "potential-regression" -R ${{ github.repository }} 32 | fi 33 | -------------------------------------------------------------------------------- /.github/workflows/semgrep-analysis.yml: -------------------------------------------------------------------------------- 1 | name: Semgrep 2 | 3 | on: 4 | # Scan changed files in PRs, block on new issues only (existing issues ignored) 5 | pull_request: 6 | 7 | push: 8 | branches: ["dev", "main"] 9 | 10 | schedule: 11 | - cron: '23 20 * * 1' 12 | 13 | # Manually trigger the workflow 14 | workflow_dispatch: 15 | 16 | jobs: 17 | semgrep: 18 | name: Scan 19 | permissions: 20 | security-events: write 21 | runs-on: ubuntu-latest 22 | container: 23 | image: returntocorp/semgrep 24 | # Skip any PR created by dependabot to avoid permission issues 25 | if: (github.actor != 'dependabot[bot]') 26 | steps: 27 | # Fetch project source 28 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 29 | 30 | - run: semgrep ci --sarif > semgrep.sarif 31 | env: 32 | SEMGREP_RULES: >- # more at semgrep.dev/explore 33 | p/security-audit 34 | p/secrets 35 | p/owasp-top-ten 36 | 37 | - name: Upload SARIF file for GitHub Advanced Security Dashboard 38 | uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 #v3.28.16 39 | with: 40 | sarif_file: semgrep.sarif 41 | if: always() 42 | -------------------------------------------------------------------------------- /.github/workflows/stale_issues.yml: -------------------------------------------------------------------------------- 1 | name: "Close stale issues" 2 | 3 | # Controls when the action will run. 4 | on: 5 | schedule: 6 | - cron: "0 0 * * *" 7 | 8 | jobs: 9 | cleanup: 10 | runs-on: ubuntu-latest 11 | name: Stale issue job 12 | steps: 13 | - uses: aws-actions/stale-issue-cleanup@v6 14 | with: 15 | # Setting messages to an empty string will cause the automation to skip 16 | # that category 17 | ancient-issue-message: We have noticed this issue has not received attention in 1 year. We will close this issue for now. If you think this is in error, please feel free to comment and reopen the issue. 18 | stale-issue-message: This issue has not received a response in 5 days. If you want to keep this issue open, please just leave a comment below and auto-close will be canceled. 19 | 20 | # These labels are required 21 | stale-issue-label: closing-soon 22 | exempt-issue-labels: no-autoclose 23 | stale-pr-label: no-pr-activity 24 | exempt-pr-labels: awaiting-approval 25 | response-requested-label: response-requested 26 | 27 | # Don't set closed-for-staleness label to skip closing very old issues 28 | # regardless of label 29 | closed-for-staleness-label: closed-for-staleness 30 | 31 | # Issue timing 32 | days-before-stale: 5 33 | days-before-close: 2 34 | days-before-ancient: 36500 35 | 36 | # If you don't want to mark a issue as being ancient based on a 37 | # threshold of "upvotes", you can set this here. An "upvote" is 38 | # the total number of +1, heart, hooray, and rocket reactions 39 | # on an issue. 40 | minimum-upvotes-to-exempt: 10 41 | 42 | repo-token: ${{ secrets.GITHUB_TOKEN }} 43 | #loglevel: DEBUG 44 | # Set dry-run to true to not perform label or close actions. 45 | #dry-run: true 46 | 47 | -------------------------------------------------------------------------------- /.github/workflows/sync-main-dev.yml: -------------------------------------------------------------------------------- 1 | 2 | # This GitHub Workflow is designed to run automatically after the Release PR, which was created by the `Create Release PR` workflow, is closed. 3 | # This workflow has 2 jobs. One will run if the `Release PR` is successfully merged, indicating that a release should go out. 4 | # The other will run if the `Release PR` was closed and a release is not intended to go out. 5 | name: Sync 'dev' and 'main' 6 | 7 | # The workflow will automatically be triggered when any PR is closed. 8 | on: 9 | pull_request: 10 | types: [closed] 11 | 12 | permissions: 13 | contents: write 14 | id-token: write 15 | 16 | jobs: 17 | # This job will check if the PR was successfully merged, it's source branch is `releases/next-release` and target branch is `dev`. 18 | # This indicates that the merged PR was the `Release PR`. 19 | # This job will synchronize `dev` and `main`, create a GitHub Release and delete the `releases/next-release` branch. 20 | sync-dev-and-main: 21 | name: Sync dev and main 22 | if: | 23 | github.event.pull_request.merged == true && 24 | github.event.pull_request.head.ref == 'releases/next-release' && 25 | github.event.pull_request.base.ref == 'dev' 26 | runs-on: ubuntu-latest 27 | steps: 28 | # Assume an AWS Role that provides access to the Access Token 29 | - name: Configure AWS Credentials 30 | uses: aws-actions/configure-aws-credentials@8c3f20df09ac63af7b3ae3d7c91f105f857d8497 #v4 31 | with: 32 | role-to-assume: ${{ secrets.RELEASE_WORKFLOW_ACCESS_TOKEN_ROLE_ARN }} 33 | aws-region: us-west-2 34 | # Retrieve the Access Token from Secrets Manager 35 | - name: Retrieve secret from AWS Secrets Manager 36 | uses: aws-actions/aws-secretsmanager-get-secrets@fbd65ea98e018858715f591f03b251f02b2316cb #v2.0.8 37 | with: 38 | secret-ids: | 39 | AWS_SECRET, ${{ secrets.RELEASE_WORKFLOW_ACCESS_TOKEN_NAME }} 40 | parse-json-secrets: true 41 | # Checkout a full clone of the repo 42 | - name: Checkout code 43 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 44 | with: 45 | ref: dev 46 | fetch-depth: 0 47 | token: ${{ env.AWS_SECRET_TOKEN }} 48 | # Install .NET8 which is needed for AutoVer 49 | - name: Setup .NET 8.0 50 | uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 #v4.3.1 51 | with: 52 | dotnet-version: 8.0.x 53 | # Install AutoVer which is needed to retrieve information about the current release. 54 | - name: Install AutoVer 55 | run: dotnet tool install --global AutoVer --version 0.0.25 56 | # Set up a git user to be able to run git commands later on 57 | - name: Setup Git User 58 | run: | 59 | git config --global user.email "github-aws-sdk-dotnet-automation@amazon.com" 60 | git config --global user.name "aws-sdk-dotnet-automation" 61 | # Retrieve the release name which is needed for the GitHub Release 62 | - name: Read Release Name 63 | id: read-release-name 64 | run: | 65 | version=$(autover changelog --release-name) 66 | echo "VERSION=$version" >> $GITHUB_OUTPUT 67 | # Retrieve the tag name which is needed for the GitHub Release 68 | - name: Read Tag Name 69 | id: read-tag-name 70 | run: | 71 | tag=$(autover changelog --tag-name) 72 | echo "TAG=$tag" >> $GITHUB_OUTPUT 73 | # Retrieve the changelog which is needed for the GitHub Release 74 | - name: Read Changelog 75 | id: read-changelog 76 | run: | 77 | changelog=$(autover changelog --output-to-console) 78 | echo "CHANGELOG<> "$GITHUB_OUTPUT" 79 | # Merge dev into main in order to synchronize the 2 branches 80 | - name: Merge dev to main 81 | run: | 82 | git fetch origin 83 | git checkout main 84 | git merge dev 85 | git push origin main 86 | # Create the GitHub Release 87 | - name: Create GitHub Release 88 | env: 89 | GITHUB_TOKEN: ${{ env.AWS_SECRET_TOKEN }} 90 | run: | 91 | gh release create "${{ steps.read-tag-name.outputs.TAG }}" --title "${{ steps.read-release-name.outputs.VERSION }}" --notes "${{ steps.read-changelog.outputs.CHANGELOG }}" 92 | # Delete the `releases/next-release` branch 93 | - name: Clean up 94 | run: | 95 | git fetch origin 96 | git push origin --delete releases/next-release 97 | # This job will check if the PR was closed, it's source branch is `releases/next-release` and target branch is `dev`. 98 | # This indicates that the closed PR was the `Release PR`. 99 | # This job will delete the tag created by AutoVer and the release branch. 100 | clean-up-closed-release: 101 | name: Clean up closed release 102 | if: | 103 | github.event.pull_request.merged == false && 104 | github.event.pull_request.head.ref == 'releases/next-release' && 105 | github.event.pull_request.base.ref == 'dev' 106 | runs-on: ubuntu-latest 107 | steps: 108 | # Checkout a full clone of the repo 109 | - name: Checkout code 110 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 111 | with: 112 | ref: releases/next-release 113 | fetch-depth: 0 114 | # Install .NET8 which is needed for AutoVer 115 | - name: Setup .NET 8.0 116 | uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 #v4.3.1 117 | with: 118 | dotnet-version: 8.0.x 119 | # Install AutoVer which is needed to retrieve information about the current release. 120 | - name: Install AutoVer 121 | run: dotnet tool install --global AutoVer --version 0.0.25 122 | # Set up a git user to be able to run git commands later on 123 | - name: Setup Git User 124 | run: | 125 | git config --global user.email "github-aws-sdk-dotnet-automation@amazon.com" 126 | git config --global user.name "aws-sdk-dotnet-automation" 127 | # Retrieve the tag name to be deleted 128 | - name: Read Tag Name 129 | id: read-tag-name 130 | run: | 131 | tag=$(autover changelog --tag-name) 132 | echo "TAG=$tag" >> $GITHUB_OUTPUT 133 | # Delete the tag created by AutoVer and the release branch 134 | - name: Clean up 135 | run: | 136 | git fetch origin 137 | git push --delete origin ${{ steps.read-tag-name.outputs.TAG }} 138 | git push origin --delete releases/next-release -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ###################################### 2 | # Visual Studio per-user settings data 3 | ###################################### 4 | *.suo 5 | *.user 6 | 7 | #################### 8 | # Build/Test folders 9 | #################### 10 | 11 | **/bin/ 12 | **/obj/ 13 | **/TestResults/ 14 | **/Temp/ 15 | **/buildlogs/ 16 | **/.vs/ 17 | 18 | **/*project.lock.json 19 | **/project.lock.json 20 | **/*.nuspec 21 | 22 | packages 23 | 24 | # JetBrains Rider 25 | .idea/ 26 | *.sln.iml -------------------------------------------------------------------------------- /Amazon.Extensions.CognitoAuthentication.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.16 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Amazon.Extensions.CognitoAuthentication", "src\Amazon.Extensions.CognitoAuthentication\Amazon.Extensions.CognitoAuthentication.csproj", "{11E95DC6-54D2-4E7F-A05B-4C40C3B6FA49}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Amazon.Extensions.CognitoAuthentication.IntegrationTests", "test\Amazon.Extensions.CognitoAuthentication.IntegrationTests\Amazon.Extensions.CognitoAuthentication.IntegrationTests.csproj", "{198278E6-752A-47AB-8AF2-4E8E3C5633BF}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Amazon.Extensions.CognitoAuthentication.UnitTests", "test\Amazon.Extensions.CognitoAuthentication.UnitTests\Amazon.Extensions.CognitoAuthentication.UnitTests.csproj", "{4A06044A-0043-4EB1-B1DB-F37B2E76FF84}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {11E95DC6-54D2-4E7F-A05B-4C40C3B6FA49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {11E95DC6-54D2-4E7F-A05B-4C40C3B6FA49}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {11E95DC6-54D2-4E7F-A05B-4C40C3B6FA49}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {11E95DC6-54D2-4E7F-A05B-4C40C3B6FA49}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {198278E6-752A-47AB-8AF2-4E8E3C5633BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {198278E6-752A-47AB-8AF2-4E8E3C5633BF}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {198278E6-752A-47AB-8AF2-4E8E3C5633BF}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {198278E6-752A-47AB-8AF2-4E8E3C5633BF}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {4A06044A-0043-4EB1-B1DB-F37B2E76FF84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {4A06044A-0043-4EB1-B1DB-F37B2E76FF84}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {4A06044A-0043-4EB1-B1DB-F37B2E76FF84}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {4A06044A-0043-4EB1-B1DB-F37B2E76FF84}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {C62404DE-2282-4A60-8D78-D42E07CCCA9C} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Release 2025-06-03 2 | 3 | ### Amazon.Extensions.CognitoAuthentication (3.1.0) 4 | * Added support for Email MFA authentication challenge. 5 | 6 | ## Release 2025-04-28 7 | 8 | ### Amazon.Extensions.CognitoAuthentication (3.0.0) 9 | * Updating the .NET SDK dependencies to the latest version GA 4.0.0 10 | 11 | ## Release 2025-04-17 12 | 13 | ### Amazon.Extensions.CognitoAuthentication (2.5.6) 14 | * Add support for UserContextData 15 | 16 | ## Release 2025-03-31 17 | 18 | ### Amazon.Extensions.CognitoAuthentication (3.0.0-preview.3) 19 | * Update AWS SDK to Preview 11 20 | 21 | ## Release 2025-02-27 22 | 23 | ### Amazon.Extensions.CognitoAuthentication (3.0.0-preview.2) 24 | * Update .NET SDK dependencies to v4.0.0-preview8 25 | 26 | ## Release 2024-10-17 27 | 28 | ### Amazon.Extensions.CognitoAuthentication (3.0.0-preview.1) 29 | * Added .NET 8 target framework and marked as trimmable 30 | * Updated the .NET SDK dependencies to the latest version 4.0.0-preview.4 31 | * Add SourceLink support 32 | 33 | ## Release 2024-07-09 34 | 35 | ### Amazon.Extensions.CognitoAuthentication (2.5.5) 36 | * Added support for analytics metadata for collecting Amazon Pinpoint metrics. 37 | 38 | ## Release 2024-05-03 39 | 40 | ### Amazon.Extensions.CognitoAuthentication (2.5.4) 41 | * Add ClientMetadata to InitiateAuthRequest during StartWithSrpAuthAsync. Thanks [willsmith9182](https://github.com/willsmith9182). 42 | 43 | ## Release 2024-04-20 44 | 45 | ### Amazon.Extensions.CognitoAuthentication (2.5.3) 46 | * Update User-Agent string 47 | 48 | ## Release 2023-10-03 49 | 50 | ### Amazon.Extensions.CognitoAuthentication (2.5.2) 51 | * Pull Request [#132](https://github.com/aws/aws-sdk-net-extensions-cognito/pull/132) Adds code improvements to make it more idiomatic. 52 | * Pull Request [#127](https://github.com/aws/aws-sdk-net-extensions-cognito/pull/127) Verifies the ChallengeName during SRP authentication. 53 | * Pull Request [#126](https://github.com/aws/aws-sdk-net-extensions-cognito/pull/126) Fixes issues with the SecretHash initialization. 54 | 55 | Thanks [DmitryProskurin](https://github.com/DmitryProskurin) for the above changes. 56 | 57 | ## Release 2023-08-30 58 | 59 | ### Amazon.Extensions.CognitoAuthentication (2.5.1) 60 | * Pull Request [#130](https://github.com/aws/aws-sdk-net-extensions-cognito/pull/130) Add ConfigureAwait(false) to avoid sync context deadlocks. Thanks [Ryan Swenson](https://github.com/swensorm) 61 | 62 | ## Release 2023-06-21 63 | 64 | ### Amazon.Extensions.CognitoAuthentication (2.5.0) 65 | * Pull Request [#123](https://github.com/aws/aws-sdk-net-extensions-cognito/pull/123) add support for software MFA. Thanks [DmitryProskurin](https://github.com/DmitryProskurin) 66 | 67 | ## Release 2023-05-18 68 | 69 | ### Amazon.Extensions.CognitoAuthentication (2.4.2) 70 | * Fix the binary compatibility bug introduced in 2.4.1 by restoring the public async method overloads without CancellationToken arguments. 71 | 72 | ## Release 2023-05-12 73 | 74 | ### Amazon.Extensions.CognitoAuthentication (2.4.1) 75 | * Pull Request [#115](https://github.com/aws/aws-sdk-net-extensions-cognito/pull/115), add optional CancellationToken arguments to async methods, thanks [GabrielHare](https://github.com/GabrielHare) 76 | 77 | ## Release 2023-03-29 78 | 79 | ### Amazon.Extensions.CognitoAuthentication (2.4.0) 80 | * Added new ListDevicesV2Async method and obsoleted ListDevicesAsync method in CognitoUser class. 81 | 82 | ## Release 2023-03-13 83 | 84 | ### Amazon.Extensions.CognitoAuthentication (2.3.1) 85 | * Pull Request [#108](https://github.com/aws/aws-sdk-net-extensions-cognito/pull/108), add caching for determining assembly version number. Thanks [mojotheo](https://github.com/mojotheo) 86 | 87 | ## Release 2023-02-08 88 | 89 | ### Amazon.Extensions.CognitoAuthentication (2.3.0) 90 | * Pull Request [#104](https://github.com/aws/aws-sdk-net-extensions-cognito/pull/104) Allow CognitoUser to be inheritant, thanks [petrenslavik](https://github.com/petrenslavik) 91 | * Pull Request [#97](https://github.com/aws/aws-sdk-net-extensions-cognito/pull/97) Add support for CUSTOM_AUTH, thanks [konectech](https://github.com/konectech) 92 | 93 | ## Release 2023-01-11 94 | 95 | ### Amazon.Extensions.CognitoAuthentication (2.2.4) 96 | * Add ClientMetadata to SRP auth flow. 97 | 98 | ## Release 2022-09-12 99 | 100 | ### Amazon.Extensions.CognitoAuthentication (2.2.3) 101 | * Allow CognitoUser.RespondToCustomAuthAsync to include ClientMetadata. 102 | 103 | ## Release 2021-07-15 104 | 105 | ### Amazon.Extensions.CognitoAuthentication (2.2.2) 106 | * Fixed an issue where IssuedTime and ExpirationTime for CognitoUserSession object should be in UTC when it is instantiated manually by user. 107 | * Removed check to validate CognitoSessionTokens which checks ExpirationTime for REFRESH_TOKEN Auth Flow. 108 | 109 | ## Release 2021-04-30 110 | 111 | ### Amazon.Extensions.CognitoAuthentication (2.2.1) 112 | * Switch all calls to DateTime.Now to DateTime.UtcNow. 113 | 114 | ## Release 2021-03-22 115 | 116 | ### Amazon.Extensions.CognitoAuthentication (2.1.0) 117 | * Added support for TOTP challenges, supports the existing way by defaulting to SMS, but also has an additional override method to allow setting the challenge type. 118 | * Make the methods of CognitoUser virtual so that mock test cases could be written for CognitoUser class. 119 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/aws/aws-sdk-net-extensions-cognito/issues), or [recently closed](https://github.com/aws/aws-sdk-net-extensions-cognito/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | ## Adding a `change file` to your contribution branch 43 | 44 | Each contribution branch should include a `change file` that contains a changelog message for each project that has been updated, as well as the type of increment to perform for those changes when versioning the project. 45 | 46 | A `change file` looks like the following example: 47 | ```json 48 | { 49 | "Projects": [ 50 | { 51 | "Name": "Amazon.Extensions.CognitoAuthentication", 52 | "Type": "Patch", 53 | "ChangelogMessages": [ 54 | "Fixed an issue causing a failure somewhere" 55 | ] 56 | } 57 | ] 58 | } 59 | ``` 60 | The `change file` lists all the modified projects, the changelog message for each project as well as the increment type. 61 | 62 | These files are located in the repo at .autover/changes/ 63 | 64 | You can use the `AutoVer` tool to create the change file. You can install it using the following command: 65 | ``` 66 | dotnet tool install -g AutoVer 67 | ``` 68 | 69 | You can create the `change file` using the following command: 70 | ``` 71 | autover change --project-name "Amazon.Extensions.CognitoAuthentication" -m "Fixed an issue causing a failure somewhere 72 | ``` 73 | Note: Make sure to run the command from the root of the repository. 74 | 75 | You can update the command to specify which project you are updating. 76 | The available projects are: 77 | * Amazon.Extensions.CognitoAuthentication 78 | 79 | The possible increment types are: 80 | * Patch 81 | * Minor 82 | * Major 83 | 84 | Note: You do not need to create a new `change file` for every changelog message or project within your branch. You can create one `change file` that contains all the modified projects and the changelog messages. 85 | 86 | ## Finding contributions to work on 87 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws/aws-sdk-net-extensions-cognito/labels/help%20wanted) issues is a great place to start. 88 | 89 | 90 | ## Code of Conduct 91 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 92 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 93 | opensource-codeofconduct@amazon.com with any additional questions or comments. 94 | 95 | 96 | ## Security issue notifications 97 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 98 | 99 | 100 | ## Licensing 101 | 102 | See the [LICENSE](https://github.com/aws/aws-sdk-net-extensions-cognito/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 103 | 104 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | AWS SDK Net Extensions Cognito 2 | Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![.NET on AWS Banner](./logo.png ".NET on AWS") 2 | 3 | ## Amazon Cognito Authentication Extension Library 4 | 5 | [![nuget](https://img.shields.io/nuget/v/Amazon.Extensions.CognitoAuthentication.svg)](https://www.nuget.org/packages/Amazon.Extensions.CognitoAuthentication/) 6 | 7 | [Amazon.Extensions.CognitoAuthentication](https://www.nuget.org/packages/Amazon.Extensions.CognitoAuthentication/) simplifies the authentication process of [Amazon Cognito User Pools](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools.html) for .NET developers. 8 | 9 | It allows you to use various authentication methods for Amazon Cognito User Pools with only a few short method calls, and makes the process intuitive. 10 | 11 | [Learn more about Amazon Cognito User Pools.](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-getting-started.html) 12 | 13 | This library targets the .NET Standard 2.0 and introduces the following dependencies: 14 | 15 | * [AWSSDK.CognitoIdentity](https://www.nuget.org/packages/AWSSDK.CognitoIdentity/) 16 | * [AWSSDK.CognitoIdentityProvider](https://www.nuget.org/packages/AWSSDK.CognitoIdentityProvider/) 17 | 18 | 19 | # Getting Started 20 | 21 | To take advantage of this library, set up an AWS account and install the AWS SDK for .NET as described in [Getting Started with the AWS SDK for .NET](https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/net-dg-setup.html). 22 | 23 | While this library is in development, you will need to build it manually. 24 | 25 | Create a new project in Visual Studio and add the Amazon Cognito Authentication Extension Library as a reference to the project. 26 | 27 | Using the library to make calls to the Amazon Cognito Identity Provider API from the AWS SDK for .NET is as simple as creating the necessary **CognitoAuthentication** objects and calling the appropriate **AmazonCognitoIdentityProviderClient** methods. The principal Amazon Cognito authentication objects are: 28 | 29 | - **CognitoUserPool** objects store information about a user pool, including the poolID, clientID, and other pool attributes. 30 | - **CognitoUser** objects contain a user’s username, the pool they are associated with, session information, and other user properties. 31 | - **CognitoDevice** objects include device information, such as the device key. 32 | 33 | ## Authenticating with Secure Remote Protocol (SRP) 34 | 35 | Instead of implementing hundreds of lines of cryptographic methods yourself, you now only need to create the necessary **AmazonCognitoIdentityProviderClient**, **CognitoUserPool**, **CognitoUser**, and **InitiateSrpAuthRequest** objects and then call **StartWithSrpAuthAsync**: 36 | 37 | 38 | ```csharp 39 | using Amazon.Runtime; 40 | using Amazon.CognitoIdentityProvider; 41 | using Amazon.Extensions.CognitoAuthentication; 42 | 43 | public async void AuthenticateWithSrpAsync() 44 | { 45 | var provider = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(), FallbackRegionFactory.GetRegionEndpoint()); 46 | var userPool = new CognitoUserPool("poolID", "clientID", provider); 47 | var user = new CognitoUser("username", "clientID", userPool, provider); 48 | 49 | var password = "userPassword"; 50 | 51 | AuthFlowResponse authResponse = await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest() 52 | { 53 | Password = password 54 | }).ConfigureAwait(false); 55 | } 56 | ``` 57 | 58 | The **AuthenticationResult** property of the **AuthFlowResponse** object contains the user’s session tokens if the user was successfully authenticated. If more challenge responses are required, this field is null and the **ChallengeName** property describes the next challenge, such as multi-factor authentication. You would then call the appropriate method to continue the authentication flow. 59 | 60 | ## Authenticating with Multiple Forms of Authentication 61 | 62 | Continuing the authentication flow with challenges, such as with **NewPasswordRequired** and **Multi-Factor Authentication (MFA)**, is simpler as well. 63 | 64 | The following code shows one way to check the challenge type and get appropriate responses for MFA and NewPasswordRequired challenges. This processing might be necessary as the authentication flow proceeds, depending on the properties of the **AuthFlowResponse** object that was retrieved earlier. 65 | 66 | ```csharp 67 | while (authResponse.AuthenticationResult == null) 68 | { 69 | if (authResponse.ChallengeName == ChallengeNameType.NEW_PASSWORD_REQUIRED) 70 | { 71 | Console.WriteLine("Enter your desired new password:"); 72 | string newPassword = Console.ReadLine(); 73 | 74 | authResponse = 75 | await user.RespondToNewPasswordRequiredAsync(new RespondToNewPasswordRequiredRequest() 76 | { 77 | SessionID = authResponse.SessionID, 78 | NewPassword = newPassword 79 | }).ConfigureAwait(false); 80 | } 81 | else if (authResponse.ChallengeName == ChallengeNameType.SMS_MFA) 82 | { 83 | Console.WriteLine("Enter the MFA Code sent to your device:"); 84 | string mfaCode = Console.ReadLine(); 85 | 86 | authResponse = await user.RespondToSmsMfaAuthAsync(new RespondToSmsMfaRequest() 87 | { 88 | SessionID = authResponse.SessionID, 89 | MfaCode = mfaCode 90 | }).ConfigureAwait(false); 91 | } 92 | else 93 | { 94 | Console.WriteLine("Unrecognized authentication challenge."); 95 | break; 96 | } 97 | } 98 | ``` 99 | 100 | [Learn more about Amazon Cognito User Pool Authentication Flow.](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html) 101 | 102 | ## Authenticating with Different Levels of Authentication 103 | 104 | After a user is authenticated by using the Amazon Cognito Authentication Extension Library, you can then allow them to access specific AWS resources. 105 | 106 | To allow users to access specific AWS resources, you must create an identity pool through the **Amazon Cognito Federated Identities** console. 107 | 108 | You can also specify different roles for both unauthenticated and authenticated users so that they can access different resources. 109 | These roles can be changed in the IAM console where you can add or remove permissions in the **Action** field of the role’s attached policy. Then, using the appropriate identity pool, user pool, and Amazon Cognito user information, calls can be made to different AWS resources. 110 | 111 | The following code shows how a user, who was authenticated with SRP, can access various S3 buckets as permitted by the associated identity pool’s role. 112 | 113 | ```csharp 114 | using Amazon; 115 | using Amazon.Runtime; 116 | using Amazon.S3; 117 | using Amazon.S3.Model; 118 | using Amazon.CognitoIdentity; 119 | using Amazon.CognitoIdentityProvider; 120 | using Amazon.Extensions.CognitoAuthentication; 121 | 122 | public async void GetS3BucketsAsync() 123 | { 124 | var provider = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(), 125 | FallbackRegionFactory.GetRegionEndpoint()); 126 | var userPool = new CognitoUserPool("poolID", "clientID", provider); 127 | var user = new CognitoUser("username", "clientID", userPool, provider); 128 | 129 | var password = "userPassword"; 130 | 131 | await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest() 132 | { 133 | Password = password 134 | }).ConfigureAwait(false); 135 | 136 | var credentials = 137 | user.GetCognitoAWSCredentials("identityPoolID", RegionEndpoint.); 138 | 139 | using (var client = new AmazonS3Client(credentials)) 140 | { 141 | ListBucketsResponse response = 142 | await client.ListBucketsAsync(new ListBucketsRequest()).ConfigureAwait(false); 143 | 144 | foreach (S3Bucket bucket in response.Buckets) 145 | { 146 | Console.WriteLine(bucket.BucketName); 147 | } 148 | } 149 | } 150 | ``` 151 | 152 | ## Authenticating using a Refresh Token from a Previous Session 153 | 154 | Access and ID tokens provided by Cognito are only valid for one hour but the refresh token can be configured to be valid for much longer. Below is an example of how to retrieve new Access and ID tokens using a refresh token which is still valid. 155 | 156 | See [here](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html) to learn more about using the tokens returned by Amazon Cognito. 157 | 158 | ```csharp 159 | using Amazon; 160 | using Amazon.Runtime; 161 | using Amazon.CognitoIdentity; 162 | using Amazon.CognitoIdentityProvider; 163 | using Amazon.Extensions.CognitoAuthentication; 164 | 165 | public async Task GetCredsFromRefreshAsync(string refreshToken, string deviceKey) 166 | { 167 | using var provider = new AmazonCognitoIdentityProviderClient(); 168 | var userPool = new CognitoUserPool("poolID", "clientID", provider); 169 | 170 | var user = new CognitoUser("username", "clientID", userPool, provider, "clientSecret") 171 | { 172 | SessionTokens = new CognitoUserSession(null, null, refreshToken, DateTime.UtcNow, DateTime.UtcNow.AddHours(1)) 173 | }; 174 | 175 | // If the user pool is configured to track and remember user devices, it must be attached to the user before initiating the flow: 176 | // user.Device = new CognitoDevice(new DeviceType { DeviceKey = deviceKey }, user); 177 | 178 | var authResponse = await user.StartWithRefreshTokenAuthAsync(new InitiateRefreshTokenAuthRequest 179 | { 180 | AuthFlowType = AuthFlowType.REFRESH_TOKEN_AUTH 181 | }); 182 | } 183 | ``` 184 | 185 | ## Other Forms of Authentication 186 | 187 | In addition to SRP, NewPasswordRequired, MFA and Refresh the Amazon Cognito Authentication Extension Library offers an easier authentication flow for the following: 188 | 189 | - **Custom** – Begins with a call to StartWithCustomAuthAsync(InitiateCustomAuthRequest customRequest) 190 | - **AdminNoSRP** – Begins with a call to StartWithAdminNoSrpAuth(InitiateAdminNoSrpAuthRequest adminAuthRequest) 191 | 192 | # Getting Help 193 | 194 | We use the [GitHub issues](https://github.com/aws/aws-sdk-net-extensions-cognito/issues) for tracking bugs and feature requests and have limited bandwidth to address them. 195 | 196 | If you think you may have found a bug, please open an [issue](https://github.com/aws/aws-sdk-net-extensions-cognito/issues/new) 197 | 198 | # Contributing 199 | 200 | We welcome community contributions and pull requests. See 201 | [CONTRIBUTING](./CONTRIBUTING.md) for information on how to set up a development 202 | environment and submit code. 203 | 204 | # Additional Resources 205 | 206 | [AWS .NET GitHub Home Page](https://github.com/aws/dotnet) 207 | GitHub home for .NET development on AWS. You'll find libraries, tools, and resources to help you build .NET applications and services on AWS. 208 | 209 | [AWS Developer Center - Explore .NET on AWS](https://aws.amazon.com/developer/language/net/) 210 | Find .NET code samples, step-by-step guides, videos, blog content, tools, and information about live events all in one place. 211 | 212 | [AWS Developer Blog - .NET](https://aws.amazon.com/blogs/developer/category/programing-language/dot-net/) 213 | Come and see what .NET developers at AWS are up to! Learn about new .NET software announcements, guides, and how-to's. 214 | 215 | [@dotnetonaws](https://twitter.com/dotnetonaws) 216 | Follow us on twitter! 217 | 218 | # License 219 | 220 | Libraries in this repository are licensed under the Apache 2.0 License. 221 | 222 | See [LICENSE](./LICENSE) and [NOTICE](./NOTICE) for more information. 223 | -------------------------------------------------------------------------------- /buildtools/ci.buildspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | phases: 4 | install: 5 | runtime-versions: 6 | dotnet: 8.x 7 | build: 8 | commands: 9 | - dotnet test test/Amazon.Extensions.CognitoAuthentication.UnitTests/Amazon.Extensions.CognitoAuthentication.UnitTests.csproj -c Release --logger trx --results-directory ./testresults 10 | - dotnet test test/Amazon.Extensions.CognitoAuthentication.IntegrationTests/Amazon.Extensions.CognitoAuthentication.IntegrationTests.csproj -c Release --logger trx --results-directory ./testresults 11 | reports: 12 | aws-ssm-data-protection-provider-for-aspnet-tests: 13 | file-format: VisualStudioTrx 14 | files: 15 | - '**/*' 16 | base-directory: './testresults' -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-sdk-net-extensions-cognito/154d01d7cad3a13d2a10ffa7d725c693747f27d7/icon.png -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-sdk-net-extensions-cognito/154d01d7cad3a13d2a10ffa7d725c693747f27d7/logo.png -------------------------------------------------------------------------------- /public.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-sdk-net-extensions-cognito/154d01d7cad3a13d2a10ffa7d725c693747f27d7/public.snk -------------------------------------------------------------------------------- /src/Amazon.Extensions.CognitoAuthentication/Amazon.Extensions.CognitoAuthentication.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;net8.0 5 | Amazon.Extensions.CognitoAuthentication 6 | Amazon.Extensions.CognitoAuthentication 7 | Amazon.Extensions.CognitoAuthentication 8 | Amazon Cognito Authentication Extension Library 9 | Amazon Web Services 10 | An extension library to assist in the Amazon Cognito User Pools authentication process. 11 | AWS;Amazon;aws-sdk-v4;Cognito 12 | https://github.com/aws/aws-sdk-net-extensions-cognito/ 13 | LICENSE 14 | icon.png 15 | https://github.com/aws/aws-sdk-net-extensions-cognito/ 16 | Amazon Web Services 17 | true 18 | ..\..\public.snk 19 | true 20 | true 21 | true 22 | true 23 | snupkg 24 | 3.1.0 25 | 26 | 27 | 28 | true 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/Amazon.Extensions.CognitoAuthentication/CognitoAuthenticationClasses.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | using System.Collections.Generic; 17 | 18 | using Amazon.CognitoIdentityProvider; 19 | using Amazon.CognitoIdentityProvider.Model; 20 | 21 | namespace Amazon.Extensions.CognitoAuthentication 22 | { 23 | /// 24 | /// Class necessary for allowing users to respond to authentication results 25 | /// at each step of the authentication flow 26 | /// 27 | public class AuthFlowResponse 28 | { 29 | /// 30 | /// Constructs an AuthFlowResponse object 31 | /// 32 | /// The authentication workflow session id. 33 | /// The result of the Authentication workflow 34 | /// The challenge name if any. 35 | /// The challenge parameters if any. 36 | /// The client metadata. 37 | public AuthFlowResponse(string sessionId, AuthenticationResultType authenticationResult, ChallengeNameType challengeName, IDictionary challengeParameters, IDictionary clientMetadata) 38 | { 39 | SessionID = sessionId; 40 | ChallengeName = challengeName; 41 | AuthenticationResult = authenticationResult; 42 | ChallengeParameters = challengeParameters ?? new Dictionary(); 43 | ClientMetadata = clientMetadata ?? new Dictionary(); 44 | } 45 | 46 | /// 47 | /// The sessionID for the current authentication flow. 48 | /// 49 | public string SessionID { get; } 50 | 51 | /// 52 | /// The current challenge name for the authentication flow. 53 | /// 54 | public ChallengeNameType ChallengeName { get; } 55 | 56 | /// 57 | /// The current authentication result for the authentication flow. 58 | /// 59 | public AuthenticationResultType AuthenticationResult { get; } 60 | 61 | /// 62 | /// The challenge parameters for the current authentication flow. 63 | /// 64 | public IDictionary ChallengeParameters { get; } 65 | 66 | /// 67 | /// The client metadata for the current authentication flow. Only 68 | /// applicable for custom authentication. 69 | /// 70 | public IDictionary ClientMetadata { get; } 71 | 72 | /// 73 | /// The analytics metadata for collecting Amazon Pinpoint metrics. 74 | /// 75 | public AnalyticsMetadataType AnalyticsMetadata { get; set; } 76 | 77 | /// 78 | /// Additional UserContextDataType 79 | /// 80 | public UserContextDataType UserContextData { get; set; } 81 | } 82 | 83 | /// 84 | /// Class containing the necessary properties to initiate SRP authentication flow 85 | /// 86 | public class InitiateSrpAuthRequest 87 | { 88 | /// 89 | /// The password for the corresponding CognitoUser. 90 | /// 91 | public string Password { get; set; } 92 | /// 93 | /// The password for the device associated with the corresponding CognitoUser 94 | /// 95 | public string DevicePass { get; set; } 96 | /// 97 | /// The device password verifier for the device associated with the corresponding CognitoUser 98 | /// 99 | public string DeviceVerifier { get; set; } 100 | /// 101 | /// The Device Key Group for the device associated with the corresponding CognitoUser 102 | /// 103 | public string DeviceGroupKey { get; set; } 104 | /// 105 | /// The client metadata for the current authentication flow. 106 | /// 107 | public IDictionary ClientMetadata { get; set; } 108 | /// 109 | /// The analytics metadata for collecting Amazon Pinpoint metrics. 110 | /// 111 | public AnalyticsMetadataType AnalyticsMetadata { get; set; } 112 | 113 | /// 114 | /// Additional UserContextDataType 115 | /// 116 | public UserContextDataType UserContextData { get; set; } 117 | 118 | /// 119 | /// Enable custom auth flow 120 | /// https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html#Using-SRP-password-verification-in-custom-authentication-flow 121 | /// 122 | public bool IsCustomAuthFlow { get; set; } 123 | } 124 | 125 | /// 126 | /// Class containing the necessary properties to initiate custom authentication flow 127 | /// 128 | public class InitiateCustomAuthRequest 129 | { 130 | /// 131 | /// The authentication parameters for the current authentication flow. 132 | /// 133 | public IDictionary AuthParameters { get; set; } 134 | 135 | /// 136 | /// The client metadata for the current authentication flow. 137 | /// 138 | public IDictionary ClientMetadata { get; set; } 139 | 140 | /// 141 | /// The analytics metadata for collecting Amazon Pinpoint metrics. 142 | /// 143 | public AnalyticsMetadataType AnalyticsMetadata { get; set; } 144 | 145 | /// 146 | /// Additional UserContextDataType 147 | /// 148 | public UserContextDataType UserContextData { get; set; } 149 | } 150 | 151 | /// 152 | /// Class containing the necessary properties to initiate either REFRESH_TOKEN or 153 | /// REFRESH_TOKEN_AUTH authentication 154 | /// 155 | public class InitiateRefreshTokenAuthRequest 156 | { 157 | /// 158 | /// The authentication flow type for the current authentication flow; 159 | /// either REFRESH_TOKEN or REFRESH_TOKEN_AUTH 160 | /// 161 | public AuthFlowType AuthFlowType { get; set; } 162 | } 163 | 164 | /// 165 | /// Class containing the necessary properties to respond to an MFA authentication challenge 166 | /// 167 | public class RespondToMfaRequest 168 | { 169 | /// 170 | /// The session ID for the current authentication flow. 171 | /// 172 | public virtual string SessionID { get; set; } 173 | 174 | /// 175 | /// The MFA verification code needed to authenticate the user. 176 | /// 177 | public virtual string MfaCode { get; set; } 178 | 179 | /// 180 | /// The challenge name type for the current authentication flow. 181 | /// 182 | public virtual ChallengeNameType ChallengeNameType { get; set; } 183 | } 184 | 185 | /// 186 | /// Class containing the necessary properties to respond to an MFA authentication challenge 187 | /// 188 | public class RespondToSmsMfaRequest : RespondToMfaRequest 189 | { 190 | /// 191 | /// The challenge name type for the current authentication flow. 192 | /// 193 | public override ChallengeNameType ChallengeNameType { get { return ChallengeNameType.SMS_MFA; } set { } } 194 | } 195 | 196 | /// 197 | /// Class containing the necessary properties to respond to an Email MFA authentication challenge 198 | /// 199 | public class RespondToEmailMfaRequest : RespondToMfaRequest 200 | { 201 | /// 202 | /// The challenge name type for the current authentication flow. 203 | /// 204 | public override ChallengeNameType ChallengeNameType { get { return ChallengeNameType.EMAIL_OTP; } set { } } 205 | } 206 | 207 | /// 208 | /// Class containing the necessary properties to respond to a new password required authentication challenge 209 | /// 210 | public class RespondToNewPasswordRequiredRequest 211 | { 212 | /// 213 | /// The session ID for the current authentication flow. 214 | /// 215 | public string SessionID { get; set; } 216 | 217 | /// 218 | /// The new desired password for the user. 219 | /// 220 | public string NewPassword { get; set; } 221 | } 222 | 223 | /// 224 | /// Class containing the necessary properties to respond to a custom authentication challenge 225 | /// 226 | public class RespondToCustomChallengeRequest 227 | { 228 | /// 229 | /// The authentication parameters for the current authentication flow. 230 | /// 231 | public IDictionary ChallengeParameters { get; set; } 232 | 233 | /// 234 | /// The client metadata for any custom workflows that this action triggers 235 | /// 236 | public IDictionary ClientMetadata { get; set; } = new Dictionary(); 237 | 238 | /// 239 | /// The analytics metadata for collecting Amazon Pinpoint metrics. 240 | /// 241 | public AnalyticsMetadataType AnalyticsMetadata { get; set; } 242 | 243 | /// 244 | /// The sessionID for the current authentication flow. 245 | /// 246 | public string SessionID { get; set; } 247 | 248 | /// 249 | /// Additional UserContextDataType 250 | /// 251 | public UserContextDataType UserContextData { get; set; } 252 | } 253 | 254 | /// 255 | /// Class containing the necessary parameters to inititate ADMIN_NO_SRP authentication 256 | /// 257 | public class InitiateAdminNoSrpAuthRequest 258 | { 259 | /// 260 | /// The associated user's password 261 | /// 262 | public string Password { get; set; } 263 | 264 | /// 265 | /// Optional client metadata to provide in the Initiate Admin Authentication API call 266 | /// 267 | public IDictionary ClientMetadata { get; set; } 268 | 269 | /// 270 | /// Optional analytics metadata for collecting Amazon Pinpoint metrics. 271 | /// 272 | public AnalyticsMetadataType AnalyticsMetadata { get; set; } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/Amazon.Extensions.CognitoAuthentication/CognitoDevice.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | using System; 17 | using System.Threading.Tasks; 18 | using System.Collections.Generic; 19 | using System.Threading; 20 | using Amazon.CognitoIdentityProvider; 21 | using Amazon.CognitoIdentityProvider.Model; 22 | using Amazon.Extensions.CognitoAuthentication.Util; 23 | 24 | namespace Amazon.Extensions.CognitoAuthentication 25 | { 26 | public partial class CognitoDevice 27 | { 28 | /// 29 | /// The device key associated with the device. DeviceKey can only be configured 30 | /// through the constructor and the GetDevice method. 31 | /// 32 | public string DeviceKey { get; private set; } 33 | 34 | /// 35 | /// The device attributes associated with the device. DeviceAttributes can only be 36 | /// configured through the constructor and the GetDevice method. 37 | /// 38 | public Dictionary DeviceAttributes { get; private set; } 39 | 40 | /// 41 | /// The creation time for the device. CreateDate can only be configured 42 | /// through the constructor and once set cannot be changed. 43 | /// 44 | public DateTime? CreateDate { get; private set; } 45 | 46 | /// 47 | /// The last modified time for the device. LastModified can only be configured 48 | /// through the constructor and the GetDevice method. 49 | /// 50 | public DateTime? LastModified { get; private set; } 51 | 52 | /// 53 | /// The last authenticated time for the device. LastAuthenticated can only be configured 54 | /// through the constructor and the GetDevice method. 55 | /// 56 | public DateTime? LastAuthenticated { get; private set; } 57 | 58 | /// 59 | /// The user associated with the device. User can only be configured 60 | /// through the constructor, and once set it cannot be changed. 61 | /// 62 | public CognitoUser User { get; private set; } 63 | 64 | /// 65 | /// The group device key of the associated device 66 | /// 67 | public string GroupDeviceKey { get; set; } 68 | 69 | /// 70 | /// The device secret of the associated device 71 | /// 72 | public string DeviceSecret { get; set; } 73 | 74 | /// 75 | /// Creates an instance of CognitoDevice 76 | /// 77 | /// The device key for the device 78 | /// The attributes for the device 79 | /// The creation date for the device 80 | /// The last modified date for the device 81 | /// The last authenticated date for the device 82 | /// The CognitoUser associated with the device 83 | public CognitoDevice(string deviceKey, 84 | IDictionary deviceAttributes, 85 | DateTime createDate, 86 | DateTime lastModified, 87 | DateTime lastAuthenticated, 88 | CognitoUser user) 89 | { 90 | if (deviceKey == null) 91 | { 92 | throw new ArgumentNullException(nameof(deviceKey)); 93 | } 94 | 95 | if(deviceAttributes == null) 96 | { 97 | throw new ArgumentNullException(nameof(deviceAttributes)); 98 | } 99 | 100 | this.DeviceKey = deviceKey; 101 | this.DeviceAttributes = new Dictionary(deviceAttributes); 102 | this.CreateDate = createDate; 103 | this.LastModified = lastModified; 104 | this.LastAuthenticated = lastAuthenticated; 105 | this.User = user; 106 | } 107 | 108 | /// 109 | /// Creates an instance of CognitoDevice 110 | /// 111 | /// A DeviceType object to create the CognitoDevice from 112 | /// The CognitoUser associated with the device 113 | public CognitoDevice(DeviceType device, CognitoUser user) 114 | { 115 | if (device == null) 116 | { 117 | throw new ArgumentNullException(nameof(device)); 118 | } 119 | 120 | this.DeviceKey = device.DeviceKey; 121 | this.CreateDate = device.DeviceCreateDate; 122 | this.LastModified = device.DeviceLastModifiedDate; 123 | this.LastAuthenticated = device.DeviceLastAuthenticatedDate; 124 | this.User = user; 125 | this.DeviceAttributes = CreateDictionaryFromAttributeList(device.DeviceAttributes); 126 | } 127 | 128 | /// 129 | /// Gets a device attrbute based on the attribute name 130 | /// 131 | /// The name of the desired attribute 132 | /// Returns a device attrbute based on the attribute name 133 | public string GetDeviceAttribute(string attributeName) 134 | { 135 | string attributeValue = null; 136 | 137 | if (DeviceAttributes.ContainsKey(attributeName)) 138 | { 139 | attributeValue = DeviceAttributes[attributeName]; 140 | } 141 | 142 | return attributeValue; 143 | } 144 | 145 | /// 146 | /// Gets the name of the device 147 | /// 148 | /// Returns the name of the device 149 | public string GetDeviceName() 150 | { 151 | return GetDeviceAttribute(CognitoConstants.DeviceAttrName); 152 | } 153 | 154 | /// 155 | /// Gets the device from the Cognito service using the device key and user's access 156 | /// token using an asynchronous call 157 | /// 158 | public async Task GetDeviceAsync() 159 | { 160 | await GetDeviceAsync(default).ConfigureAwait(false); 161 | } 162 | 163 | /// 164 | /// Gets the device from the Cognito service using the device key and user's access 165 | /// token using an asynchronous call 166 | /// 167 | /// A cancellation token that can be used by other objects or threads to receive notice of cancellation 168 | public async Task GetDeviceAsync(CancellationToken cancellationToken) 169 | { 170 | GetDeviceRequest getDeviceRequest = CreateGetDeviceRequest(); 171 | 172 | GetDeviceResponse getDeviceResponse = 173 | await User.Provider.GetDeviceAsync(getDeviceRequest, cancellationToken).ConfigureAwait(false); 174 | 175 | UpdateThisDevice(getDeviceResponse.Device); 176 | } 177 | 178 | /// 179 | /// Forgets the device associated with the CognitoDevice's device key using 180 | /// an asynchronous call 181 | /// 182 | public Task ForgetDeviceAsync() 183 | { 184 | return ForgetDeviceAsync(default); 185 | } 186 | 187 | /// 188 | /// Forgets the device associated with the CognitoDevice's device key using 189 | /// an asynchronous call 190 | /// 191 | /// A cancellation token that can be used by other objects or threads to receive notice of cancellation 192 | public Task ForgetDeviceAsync(CancellationToken cancellationToken) 193 | { 194 | ForgetDeviceRequest forgetDeviceRequest = CreateForgetDeviceRequest(); 195 | return User.Provider.ForgetDeviceAsync(forgetDeviceRequest, cancellationToken); 196 | } 197 | 198 | /// 199 | /// Updates the device status to be remembered using an asynchronous call 200 | /// 201 | public Task RememberThisDeviceAsync() 202 | { 203 | return RememberThisDeviceAsync(default); 204 | } 205 | 206 | /// 207 | /// Updates the device status to be remembered using an asynchronous call 208 | /// 209 | /// A cancellation token that can be used by other objects or threads to receive notice of cancellation 210 | public Task RememberThisDeviceAsync(CancellationToken cancellationToken) 211 | { 212 | UpdateDeviceStatusRequest updateRequest = 213 | CreateUpdateDeviceStatusRequest(new DeviceRememberedStatusType(CognitoConstants.DeviceAttrRemembered)); 214 | 215 | return User.Provider.UpdateDeviceStatusAsync(updateRequest, cancellationToken); 216 | } 217 | 218 | /// 219 | /// Updates the device status to not be remembered using an asynchronous call 220 | /// 221 | public Task DoNotRememberThisDeviceAsync() 222 | { 223 | return DoNotRememberThisDeviceAsync(default); 224 | } 225 | 226 | /// 227 | /// Updates the device status to not be remembered using an asynchronous call 228 | /// 229 | /// A cancellation token that can be used by other objects or threads to receive notice of cancellation 230 | public Task DoNotRememberThisDeviceAsync(CancellationToken cancellationToken) 231 | { 232 | UpdateDeviceStatusRequest updateRequest = 233 | CreateUpdateDeviceStatusRequest(new DeviceRememberedStatusType(CognitoConstants.DeviceAttrNotRemembered)); 234 | 235 | return User.Provider.UpdateDeviceStatusAsync(updateRequest, cancellationToken); 236 | } 237 | 238 | private GetDeviceRequest CreateGetDeviceRequest() 239 | { 240 | if (User.SessionTokens == null || !User.SessionTokens.IsValid()) 241 | { 242 | throw new NotAuthorizedException("User is not authorized."); 243 | } 244 | 245 | GetDeviceRequest getDeviceRequest = new GetDeviceRequest() 246 | { 247 | AccessToken = User.SessionTokens.AccessToken, 248 | DeviceKey = DeviceKey 249 | }; 250 | 251 | return getDeviceRequest; 252 | } 253 | 254 | /// 255 | /// Internal method to update the properties of the current CognitoDevice object 256 | /// 257 | /// 258 | private void UpdateThisDevice(DeviceType device) 259 | { 260 | if (device == null) 261 | { 262 | throw new InternalErrorException("Service returned null object, this device was not updated."); 263 | } 264 | 265 | if (!string.Equals(device.DeviceKey, this.DeviceKey)) 266 | { 267 | throw new InternalErrorException("Device keys don't match, this device was not updated."); 268 | } 269 | 270 | DeviceAttributes = CreateDictionaryFromAttributeList(device.DeviceAttributes); 271 | LastModified = device.DeviceLastModifiedDate; 272 | LastAuthenticated = device.DeviceLastAuthenticatedDate; 273 | } 274 | 275 | private ForgetDeviceRequest CreateForgetDeviceRequest() 276 | { 277 | if (User.SessionTokens == null || !User.SessionTokens.IsValid()) 278 | { 279 | throw new NotAuthorizedException("User is not authorized."); 280 | } 281 | 282 | ForgetDeviceRequest forgetDeviceRequest = new ForgetDeviceRequest() 283 | { 284 | AccessToken = User.SessionTokens.AccessToken, 285 | DeviceKey = DeviceKey 286 | }; 287 | 288 | return forgetDeviceRequest; 289 | } 290 | 291 | private UpdateDeviceStatusRequest CreateUpdateDeviceStatusRequest(DeviceRememberedStatusType deviceRememberedStatus) 292 | { 293 | if (User.SessionTokens == null || !User.SessionTokens.IsValid()) 294 | { 295 | throw new NotAuthorizedException("User is not authorized."); 296 | } 297 | 298 | UpdateDeviceStatusRequest updateDeviceStatusRequest = new UpdateDeviceStatusRequest() 299 | { 300 | AccessToken = User.SessionTokens.AccessToken, 301 | DeviceKey = DeviceKey, 302 | DeviceRememberedStatus = deviceRememberedStatus 303 | }; 304 | 305 | return updateDeviceStatusRequest; 306 | } 307 | 308 | /// 309 | /// Internal method for creating a dictionary of attrbutes from a list of AttributeType objects 310 | /// 311 | /// 312 | /// 313 | private Dictionary CreateDictionaryFromAttributeList(IList attributes) 314 | { 315 | Dictionary attributesDict = new Dictionary(); 316 | 317 | foreach(AttributeType attribute in attributes ?? new List()) 318 | { 319 | attributesDict.Add(attribute.Name, attribute.Value); 320 | } 321 | 322 | return attributesDict; 323 | } 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /src/Amazon.Extensions.CognitoAuthentication/CognitoUserPool.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | using System; 17 | using System.Threading.Tasks; 18 | using System.Collections.Generic; 19 | 20 | using Amazon.CognitoIdentityProvider; 21 | using Amazon.CognitoIdentityProvider.Model; 22 | using System.Linq; 23 | using Amazon.Extensions.CognitoAuthentication.Util; 24 | using System.Threading; 25 | 26 | namespace Amazon.Extensions.CognitoAuthentication 27 | { 28 | public partial class CognitoUserPool 29 | { 30 | /// 31 | /// The poolID associated with the user pool. PoolID can only be configured 32 | /// through the constructor, and once set it cannot be changed. 33 | /// 34 | public string PoolID { get; private set; } 35 | 36 | /// 37 | /// The clientID associated with the user pool. ClientID can only be configured 38 | /// through the constructor, and once set it cannot be changed. 39 | /// 40 | public string ClientID { get; private set; } 41 | 42 | /// 43 | /// The ClientConfiguration associated with the user pool and the ClientID. 44 | /// 45 | private CognitoUserPoolClientConfiguration ClientConfiguration { get; set; } 46 | 47 | internal IAmazonCognitoIdentityProvider Provider { get; set; } 48 | 49 | private string ClientSecret { get; set; } 50 | 51 | /// 52 | /// Create an instance of CognitoUserPool 53 | /// 54 | /// PoolID of the associated user pool 55 | /// ClientID for the associated user pool 56 | /// IAmazonCognitoIdentityProvider for the specified user pool 57 | /// Client secret for the corresponding clientID, if exists 58 | public CognitoUserPool(string poolID, 59 | string clientID, 60 | IAmazonCognitoIdentityProvider provider, 61 | string clientSecret = null) 62 | { 63 | if(!poolID.Contains("_")) 64 | { 65 | throw new ArgumentException($"{nameof(poolID)} should be of the form _.", nameof(poolID)); 66 | } 67 | 68 | this.PoolID = poolID; 69 | this.ClientID = clientID; 70 | this.ClientSecret = clientSecret; 71 | 72 | this.Provider = provider; 73 | 74 | if (this.Provider is AmazonCognitoIdentityProviderClient eventProvider) 75 | { 76 | eventProvider.BeforeRequestEvent += CognitoAuthHelper.ServiceClientBeforeRequestEvent; 77 | } 78 | } 79 | 80 | /// 81 | /// Signs up the user with the specified parameters using an asynchronous call 82 | /// 83 | /// The userID of the user being created 84 | /// The password of the user being created 85 | /// The user attributes of the user being created 86 | /// The validation data of the user being created 87 | /// Returns the delivery details for the sign up request 88 | public Task SignUpAsync(string userID, 89 | string password, 90 | IDictionary userAttributes, 91 | IDictionary validationData) 92 | { 93 | return SignUpAsync(userID, password, userAttributes, validationData, default); 94 | } 95 | 96 | /// 97 | /// Signs up the user with the specified parameters using an asynchronous call 98 | /// 99 | /// The userID of the user being created 100 | /// The password of the user being created 101 | /// The user attributes of the user being created 102 | /// The validation data of the user being created 103 | /// A cancellation token that can be used by other objects or threads to receive notice of cancellation 104 | /// Returns the delivery details for the sign up request 105 | public Task SignUpAsync(string userID, 106 | string password, 107 | IDictionary userAttributes, 108 | IDictionary validationData, 109 | CancellationToken cancellationToken) 110 | { 111 | SignUpRequest signUpUserRequest = CreateSignUpRequest(userID, password, userAttributes, validationData); 112 | 113 | return Provider.SignUpAsync(signUpUserRequest, cancellationToken); 114 | } 115 | 116 | /// 117 | /// Internal method to aid in the sign up flow for a new user 118 | /// 119 | /// The userID of the user being created 120 | /// The password of the user being created 121 | /// The user attributes of the user being created 122 | /// The validation data of the user being created 123 | /// Returns the SignUpResponse for the sign up API request using the provided information 124 | private SignUpRequest CreateSignUpRequest(string userID, 125 | string password, 126 | IDictionary userAttributes, 127 | IDictionary validationData) 128 | { 129 | List userAttributesList = null; 130 | if (userAttributes != null) 131 | { 132 | userAttributesList = CognitoAuthHelper.CreateAttributeList(userAttributes); 133 | } 134 | else 135 | { 136 | throw new ArgumentNullException(nameof(userAttributes)); 137 | } 138 | 139 | List validationDataList = 140 | validationData != null ? CognitoAuthHelper.CreateAttributeList(validationData) : null; 141 | 142 | // Create User registration request 143 | SignUpRequest signUpUserRequest = new SignUpRequest() 144 | { 145 | Username = userID, 146 | Password = password, 147 | ClientId = ClientID, 148 | UserAttributes = userAttributesList, 149 | ValidationData = validationDataList 150 | }; 151 | 152 | if (!string.IsNullOrEmpty(ClientSecret)) 153 | { 154 | signUpUserRequest.SecretHash = CognitoAuthHelper.GetUserPoolSecretHash(userID, ClientID, ClientSecret); 155 | } 156 | 157 | return signUpUserRequest; 158 | } 159 | 160 | /// 161 | /// Gets a CognitoUser with no userID set 162 | /// 163 | /// Returns a user with no userID set 164 | public virtual CognitoUser GetUser() 165 | { 166 | return new CognitoUser(null, ClientID, this, Provider, ClientSecret); 167 | } 168 | 169 | /// 170 | /// Gets a CognitoUser with the corresponding userID 171 | /// 172 | /// The userID of the corresponding user 173 | /// Returns a CognitoUser with the corresponding userID 174 | public virtual CognitoUser GetUser(string userID) 175 | { 176 | if (string.IsNullOrEmpty(userID)) 177 | { 178 | return GetUser(); 179 | } 180 | 181 | return new CognitoUser(userID, ClientID, this, Provider, ClientSecret); 182 | } 183 | 184 | /// 185 | /// Gets a CognitoUser with the corresponding userID, status and attributes 186 | /// 187 | /// The userID of the corresponding user 188 | /// The status of the corresponding user 189 | /// The attributes of the corresponding user 190 | /// Returns a CognitoUser with the corresponding userID 191 | public virtual CognitoUser GetUser(string userID, string status, Dictionary attributes) 192 | { 193 | if (string.IsNullOrEmpty(userID)) 194 | { 195 | return GetUser(); 196 | } 197 | 198 | return new CognitoUser(userID, ClientID, this, Provider, ClientSecret, status, userID, attributes); 199 | } 200 | 201 | /// 202 | /// Queries Cognito and returns the CognitoUser with the corresponding userID 203 | /// 204 | /// The userID of the corresponding user 205 | /// The that represents the asynchronous operation, containing a CognitoUser with the corresponding userID, with the Status and Attributes retrieved from Cognito. 206 | public virtual async Task FindByIdAsync(string userID) 207 | { 208 | return await FindByIdAsync(userID, default).ConfigureAwait(false); 209 | } 210 | 211 | /// 212 | /// Queries Cognito and returns the CognitoUser with the corresponding userID 213 | /// 214 | /// The userID of the corresponding user 215 | /// A cancellation token that can be used by other objects or threads to receive notice of cancellation 216 | /// The that represents the asynchronous operation, containing a CognitoUser with the corresponding userID, with the Status and Attributes retrieved from Cognito. 217 | public virtual async Task FindByIdAsync(string userID, CancellationToken cancellationToken) 218 | { 219 | if (string.IsNullOrEmpty(userID)) 220 | throw new ArgumentException(nameof(userID)); 221 | 222 | try 223 | { 224 | var response = await Provider.AdminGetUserAsync(new AdminGetUserRequest 225 | { 226 | Username = userID, 227 | UserPoolId = this.PoolID 228 | }, cancellationToken).ConfigureAwait(false); 229 | 230 | return new CognitoUser(response.Username, ClientID, this, Provider, ClientSecret, 231 | response.UserStatus.Value, response.Username, 232 | response.UserAttributes?.ToDictionary(attribute => attribute.Name, attribute => attribute.Value) ?? new Dictionary()); 233 | 234 | } 235 | catch (UserNotFoundException) 236 | { 237 | return null; 238 | } 239 | } 240 | 241 | /// 242 | /// Queries Cognito and returns the PasswordPolicyType associated with the pool. 243 | /// 244 | /// The that represents the asynchronous operation, containing the PasswordPolicyType of the pool. 245 | public async Task GetPasswordPolicyTypeAsync() 246 | { 247 | return await GetPasswordPolicyTypeAsync(default).ConfigureAwait(false); 248 | } 249 | 250 | /// 251 | /// Queries Cognito and returns the PasswordPolicyType associated with the pool. 252 | /// 253 | /// A cancellation token that can be used by other objects or threads to receive notice of cancellation 254 | /// The that represents the asynchronous operation, containing the PasswordPolicyType of the pool. 255 | public async Task GetPasswordPolicyTypeAsync(CancellationToken cancellationToken) 256 | { 257 | var response = await Provider.DescribeUserPoolAsync(new DescribeUserPoolRequest 258 | { 259 | UserPoolId = this.PoolID 260 | }, cancellationToken).ConfigureAwait(false); 261 | 262 | return response.UserPool.Policies.PasswordPolicy; 263 | } 264 | 265 | /// 266 | /// Queries Cognito and returns the CognitoUserPoolClientConfiguration associated with the current pool client. 267 | /// Caches the value in the ClientConfiguration private property. 268 | /// 269 | /// The that represents the asynchronous operation, containing the PasswordPolicyType of the pool. 270 | public async Task GetUserPoolClientConfiguration() 271 | { 272 | return await GetUserPoolClientConfiguration(default).ConfigureAwait(false); 273 | } 274 | 275 | /// 276 | /// Queries Cognito and returns the CognitoUserPoolClientConfiguration associated with the current pool client. 277 | /// Caches the value in the ClientConfiguration private property. 278 | /// 279 | /// A cancellation token that can be used by other objects or threads to receive notice of cancellation 280 | /// The that represents the asynchronous operation, containing the PasswordPolicyType of the pool. 281 | public async Task GetUserPoolClientConfiguration(CancellationToken cancellationToken) 282 | { 283 | if (ClientConfiguration == null) 284 | { 285 | var response = await Provider.DescribeUserPoolClientAsync(new DescribeUserPoolClientRequest 286 | { 287 | ClientId = this.ClientID, 288 | UserPoolId = this.PoolID 289 | }, cancellationToken).ConfigureAwait(false); 290 | 291 | ClientConfiguration = new CognitoUserPoolClientConfiguration( 292 | response.UserPoolClient.ReadAttributes ?? new List(), 293 | response.UserPoolClient.WriteAttributes ?? new List()); 294 | } 295 | 296 | return ClientConfiguration; 297 | } 298 | 299 | /// 300 | /// Signs up the user with the specified parameters using an asynchronous call end triggers a temporary password sms or email message. 301 | /// 302 | /// The userID of the user being created 303 | /// The user attributes of the user being created 304 | /// The validation data of the user being created 305 | /// Returns the delivery details for the sign up request 306 | public Task AdminSignupAsync(string userID, 307 | IDictionary userAttributes, 308 | IDictionary validationData) 309 | { 310 | return AdminSignupAsync(userID, userAttributes, validationData, default); 311 | } 312 | 313 | /// 314 | /// Signs up the user with the specified parameters using an asynchronous call end triggers a temporary password sms or email message. 315 | /// 316 | /// The userID of the user being created 317 | /// The user attributes of the user being created 318 | /// The validation data of the user being created 319 | /// A cancellation token that can be used by other objects or threads to receive notice of cancellation 320 | /// Returns the delivery details for the sign up request 321 | public Task AdminSignupAsync(string userID, 322 | IDictionary userAttributes, 323 | IDictionary validationData, 324 | CancellationToken cancellationToken) 325 | { 326 | AdminCreateUserRequest signUpUserRequest = CreateAdminSignUpRequest(userID, userAttributes, validationData); 327 | 328 | return Provider.AdminCreateUserAsync(signUpUserRequest, cancellationToken); 329 | } 330 | 331 | /// 332 | /// Internal method to aid in the admin sign up flow for a new user 333 | /// 334 | /// The userID of the user being created 335 | /// The user attributes of the user being created 336 | /// The validation data of the user being created 337 | /// Returns the SignUpResponse for the sign up API request using the provided information 338 | private AdminCreateUserRequest CreateAdminSignUpRequest(string userID, 339 | IDictionary userAttributes, 340 | IDictionary validationData) 341 | { 342 | List userAttributesList = null; 343 | if (userAttributes != null) 344 | { 345 | userAttributesList = CognitoAuthHelper.CreateAttributeList(userAttributes); 346 | } 347 | else 348 | { 349 | throw new ArgumentNullException(nameof(userAttributes)); 350 | } 351 | 352 | List validationDataList = 353 | validationData != null ? CognitoAuthHelper.CreateAttributeList(validationData) : null; 354 | 355 | // Create User registration request 356 | return new AdminCreateUserRequest() 357 | { 358 | Username = userID, 359 | UserPoolId = this.PoolID, 360 | UserAttributes = userAttributesList, 361 | ValidationData = validationDataList 362 | }; 363 | } 364 | 365 | /// 366 | /// Resets the user's password to the specified after 367 | /// validating the given password reset . 368 | /// 369 | /// The ID of user whose password should be reset. 370 | /// The password reset token to verify. 371 | /// The new password to set if reset token verification succeeds. 372 | /// A cancellation token that can be used by other objects or threads to receive notice of cancellation 373 | /// 374 | /// The that represents the asynchronous operation, containing the . 375 | /// 376 | public Task ConfirmForgotPassword(string userID, string token, string newPassword, CancellationToken cancellationToken) 377 | { 378 | cancellationToken.ThrowIfCancellationRequested(); 379 | 380 | var request = new ConfirmForgotPasswordRequest 381 | { 382 | Username = userID, 383 | ClientId = ClientID, 384 | ConfirmationCode = token, 385 | Password = newPassword, 386 | 387 | }; 388 | 389 | if (!string.IsNullOrEmpty(ClientSecret)) 390 | { 391 | request.SecretHash = CognitoAuthHelper.GetUserPoolSecretHash(userID, ClientID, ClientSecret); 392 | } 393 | 394 | return Provider.ConfirmForgotPasswordAsync(request, cancellationToken); 395 | } 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /src/Amazon.Extensions.CognitoAuthentication/CognitoUserPoolClientConfiguration.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | using System; 17 | using System.Collections.Generic; 18 | 19 | namespace Amazon.Extensions.CognitoAuthentication 20 | { 21 | public class CognitoUserPoolClientConfiguration 22 | { 23 | public CognitoUserPoolClientConfiguration(List readAttributes, List writeAttributes) 24 | { 25 | ReadAttributes = readAttributes ?? throw new ArgumentNullException(nameof(readAttributes)); 26 | WriteAttributes = writeAttributes ?? throw new ArgumentNullException(nameof(writeAttributes)); 27 | } 28 | 29 | public List ReadAttributes { get; private set; } 30 | 31 | public List WriteAttributes { get; private set; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Amazon.Extensions.CognitoAuthentication/CognitoUserSession.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | using System; 17 | 18 | using Amazon.CognitoIdentityProvider; 19 | using Amazon.CognitoIdentityProvider.Model; 20 | 21 | namespace Amazon.Extensions.CognitoAuthentication 22 | { 23 | public class CognitoUserSession 24 | { 25 | /// 26 | /// The ID token associated with the current user session. 27 | /// 28 | public string IdToken { get; set; } 29 | 30 | /// 31 | /// The access token associated with the current user session. 32 | /// 33 | public string AccessToken { get; set; } 34 | 35 | /// 36 | /// The refresh token associated with the current user session. 37 | /// 38 | public string RefreshToken { get; set; } 39 | 40 | /// 41 | /// The expiration time associated with the current session's tokens. ExpirationTime 42 | /// can only be configured through the constructor, and once set it cannot be changed. 43 | /// 44 | public DateTime ExpirationTime { get; private set; } 45 | 46 | /// 47 | /// The issue time associated with the current session's tokens. IssueTime 48 | /// can only be configured through the constructor, and once set it cannot be changed. 49 | /// 50 | public DateTime IssuedTime { get; private set; } 51 | 52 | /// 53 | /// Creates an instance of CognitoUserSession 54 | /// 55 | /// The ID token for the current user session 56 | /// The access token for the current user session 57 | /// The refresh token for the current user session 58 | /// The time the tokens were issued 59 | /// The time the tokens expire 60 | public CognitoUserSession(string idToken, string accessToken, string refreshToken, 61 | DateTime issuedTime, DateTime expirationTime) 62 | { 63 | this.IdToken = idToken; 64 | this.AccessToken = accessToken; 65 | this.RefreshToken = refreshToken; 66 | this.IssuedTime = issuedTime.ToUniversalTime(); 67 | this.ExpirationTime = expirationTime.ToUniversalTime(); 68 | } 69 | 70 | /// 71 | /// Determines if the tokens for a user are still valid 72 | /// 73 | /// Returns a boolean whether the user's tokens are still valid 74 | public bool IsValid() 75 | { 76 | DateTime currentTimeStamp = DateTime.UtcNow; 77 | 78 | return (currentTimeStamp.CompareTo(ExpirationTime) < 0); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Amazon.Extensions.CognitoAuthentication/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | // Setting ComVisible to false makes the types in this assembly not visible 4 | // to COM components. If you need to access a type in this assembly from 5 | // COM, set the ComVisible attribute to true on that type. 6 | [assembly: ComVisible(false)] -------------------------------------------------------------------------------- /src/Amazon.Extensions.CognitoAuthentication/Util/AuthenticationHelper.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | using System; 17 | using System.Globalization; 18 | using System.Linq; 19 | using System.Numerics; 20 | using System.Security.Cryptography; 21 | using System.Text; 22 | using Amazon.CognitoIdentityProvider.Model; 23 | 24 | namespace Amazon.Extensions.CognitoAuthentication.Util 25 | { 26 | /// 27 | /// Class that provides utility methods for performing the Secure Remote Password protocol 28 | /// Adapted from http://srp.stanford.edu/design.html 29 | /// 30 | internal static class AuthenticationHelper 31 | { 32 | /// 33 | /// 3072-bit 34 | /// 35 | private const string Srp_hexN = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF"; 36 | 37 | private const int Srp_g = 2; 38 | 39 | public static readonly BigInteger N = BigIntegerExtensions.FromUnsignedLittleEndianHex(Srp_hexN); 40 | public static readonly BigInteger g = new BigInteger(Srp_g); 41 | public static readonly BigInteger k; 42 | 43 | /// 44 | /// Secret key size bytes 45 | /// 46 | public const int SecretKeySizeBytes = 128; 47 | 48 | /// 49 | /// Value used by AWS Cognito 50 | /// 51 | public const int DerivedKeySizeBytes = 16; 52 | 53 | public const string DerivedKeyInfo = "Caldera Derived Key"; 54 | 55 | static AuthenticationHelper() 56 | { 57 | // generate k for the input key material to HKDF 58 | var content = CognitoAuthHelper.CombineBytes(N.ToBigEndianByteArray(), g.ToBigEndianByteArray()); 59 | var messageDigest = CognitoAuthHelper.Sha256.ComputeHash(content); 60 | k = BigIntegerExtensions.FromUnsignedBigEndian(messageDigest); 61 | } 62 | 63 | /// 64 | /// Return the Tuple of (A, a) for SRP 65 | /// 66 | /// 67 | public static Tuple CreateAaTuple() 68 | { 69 | var a = CreateBigIntegerRandom(); 70 | var A = BigInteger.ModPow(g, a, N); 71 | return Tuple.Create(A, a); 72 | } 73 | 74 | /// 75 | /// Generates the claim for authenticating a user through the SRP protocol 76 | /// 77 | /// Username of CognitoUser 78 | /// Password of CognitoUser 79 | /// PoolName of CognitoUserPool (from poolID: _) 80 | /// TupleAa from CreateAaTuple 81 | /// salt provided in ChallengeParameters from Cognito 82 | /// srpb provided in ChallengeParameters from Cognito 83 | /// secret block provided in ChallengeParameters from Cognito 84 | /// En-US Culture of Current Time 85 | /// Returns the claim for authenticating the given user 86 | public static byte[] AuthenticateUser( 87 | string username, 88 | string password, 89 | string poolName, 90 | Tuple tupleAa, 91 | string saltString, 92 | string srpbString, 93 | string secretBlockBase64, 94 | string formattedTimestamp) 95 | { 96 | var B = BigIntegerExtensions.FromUnsignedLittleEndianHex(srpbString); 97 | if (B.TrueMod(N).Equals(BigInteger.Zero)) throw new ArgumentException("B mod N cannot be zero.", nameof(srpbString)); 98 | 99 | var secretBlockBytes = Convert.FromBase64String(secretBlockBase64); 100 | var salt = BigIntegerExtensions.FromUnsignedLittleEndianHex(saltString); 101 | 102 | // Need to generate the key to hash the response based on our A and what AWS sent back 103 | var key = GetPasswordAuthenticationKey(username, password, poolName, tupleAa, B, salt); 104 | 105 | // HMAC our data with key (HKDF(S)) (the shared secret) 106 | var contentBytes = CognitoAuthHelper.CombineBytes(Encoding.UTF8.GetBytes(poolName), Encoding.UTF8.GetBytes(username), 107 | secretBlockBytes, Encoding.UTF8.GetBytes(formattedTimestamp)); 108 | 109 | using (var hashAlgorithm = new HMACSHA256(key)) 110 | { 111 | return hashAlgorithm.ComputeHash(contentBytes); 112 | } 113 | } 114 | 115 | /// 116 | /// Creates the Password Authentication Key based on the SRP protocol 117 | /// 118 | /// Username of CognitoUser 119 | /// Password of CognitoUser 120 | /// PoolName of CognitoUserPool (part of poolID after "_") 121 | /// Returned from TupleAa 122 | /// BigInteger SRPB from AWS ChallengeParameters 123 | /// BigInteger salt from AWS ChallengeParameters 124 | /// Returns the password authentication key for the SRP protocol 125 | public static byte[] GetPasswordAuthenticationKey( 126 | string userID, 127 | string userPassword, 128 | string poolName, 129 | Tuple Aa, 130 | BigInteger B, 131 | BigInteger salt) 132 | { 133 | // Authenticate the password 134 | // u = H(A, B) 135 | byte[] contentBytes = CognitoAuthHelper.CombineBytes(Aa.Item1.ToBigEndianByteArray(), B.ToBigEndianByteArray()); 136 | byte[] digest = CognitoAuthHelper.Sha256.ComputeHash(contentBytes); 137 | 138 | BigInteger u = BigIntegerExtensions.FromUnsignedBigEndian(digest); 139 | if (u.Equals(BigInteger.Zero)) 140 | { 141 | throw new ArgumentException("Hash of A and B cannot be zero."); 142 | } 143 | 144 | // x = H(salt | H(poolName | userId | ":" | password)) 145 | byte[] userIdContent = CognitoAuthHelper.CombineBytes(Encoding.UTF8.GetBytes(poolName), Encoding.UTF8.GetBytes(userID), 146 | Encoding.UTF8.GetBytes(":"), Encoding.UTF8.GetBytes(userPassword)); 147 | byte[] userIdHash = CognitoAuthHelper.Sha256.ComputeHash(userIdContent); 148 | byte[] xBytes = CognitoAuthHelper.CombineBytes(salt.ToBigEndianByteArray(), userIdHash); 149 | 150 | byte[] xDigest = CognitoAuthHelper.Sha256.ComputeHash(xBytes); 151 | BigInteger x = BigIntegerExtensions.FromUnsignedBigEndian(xDigest); 152 | 153 | // Use HKDF to get final password authentication key 154 | var first = (B - k * BigInteger.ModPow(g, x, N)).TrueMod(N); 155 | var second = BigInteger.ModPow(first, Aa.Item2 + u * x, N); 156 | HkdfSha256 hkdfSha256 = new HkdfSha256(u.ToBigEndianByteArray(), second.ToBigEndianByteArray()); 157 | return hkdfSha256.Expand(Encoding.UTF8.GetBytes(DerivedKeyInfo), DerivedKeySizeBytes); 158 | } 159 | 160 | /// 161 | /// Generates a DeviceSecretVerifierConfigType object based on a CognitoDevice's Key, Group Key, and Password 162 | /// 163 | /// The Group Key of the CognitoDevice 164 | /// A random password for the CognitoDevice (used in the future for logging in via this device) 165 | /// The username of the CognitoDevice user 166 | /// 167 | public static DeviceSecretVerifierConfigType GenerateDeviceVerifier(string deviceGroupKey, string devicePass, string username) 168 | { 169 | Random r = new Random(); 170 | byte[] userIdContent = CognitoAuthHelper.CombineBytes( 171 | Encoding.UTF8.GetBytes(deviceGroupKey), 172 | Encoding.UTF8.GetBytes(username), 173 | Encoding.UTF8.GetBytes(":"), 174 | Encoding.UTF8.GetBytes(devicePass) 175 | ); 176 | 177 | byte[] userIdHash = CognitoAuthHelper.Sha256.ComputeHash(userIdContent); 178 | 179 | byte[] saltBytes = new byte[16]; 180 | RandomNumberGenerator.Create().GetBytes(saltBytes); 181 | // setting the initial byte to 0-127 to avoid negative salt or password verifier error 182 | saltBytes[0] = (byte) r.Next(sbyte.MaxValue); 183 | 184 | byte[] xBytes = CognitoAuthHelper.CombineBytes(saltBytes, userIdHash); 185 | byte[] xDigest = CognitoAuthHelper.Sha256.ComputeHash(xBytes); 186 | BigInteger x = BigIntegerExtensions.FromUnsignedBigEndian(xDigest); 187 | 188 | var v = BigInteger.ModPow(g, x, N); 189 | byte[] vBytes = v.ToBigEndianByteArray(); 190 | 191 | return new DeviceSecretVerifierConfigType 192 | { 193 | PasswordVerifier = Convert.ToBase64String(vBytes), 194 | Salt = Convert.ToBase64String(saltBytes) 195 | }; 196 | } 197 | 198 | /// 199 | /// Generates the claim for authenticating a device through the SRP protocol 200 | /// 201 | /// Username of Cognito User 202 | /// Key of CognitoDevice 203 | /// Password of CognitoDevice 204 | /// GroupKey of CognitoDevice 205 | /// salt provided in ChallengeParameters from Cognito 206 | /// srpb provided in ChallengeParameters from Cognito 207 | /// secret block provided in ChallengeParameters from Cognito 208 | /// En-US Culture of Current Time 209 | /// TupleAa from CreateAaTuple 210 | /// Returns the claim for authenticating the given user 211 | public static byte[] AuthenticateDevice( 212 | string username, 213 | string deviceKey, 214 | string devicePassword, 215 | string deviceGroupKey, 216 | string saltString, 217 | string srpbString, 218 | string secretBlockBase64, 219 | string formattedTimestamp, 220 | Tuple tupleAa) 221 | 222 | { 223 | var B = BigIntegerExtensions.FromUnsignedLittleEndianHex(srpbString); 224 | if (B.TrueMod(N).Equals(BigInteger.Zero)) throw new ArgumentException("B mod N cannot be zero.", nameof(srpbString)); 225 | 226 | var salt = BigIntegerExtensions.FromUnsignedLittleEndianHex(saltString); 227 | var secretBlockBytes = Convert.FromBase64String(secretBlockBase64); 228 | // Need to generate the key to hash the response based on our A and what AWS sent back 229 | var key = GetDeviceAuthenticationKey(username, devicePassword, deviceGroupKey, tupleAa, B, salt); 230 | 231 | // HMAC our data with key (HKDF(S)) (the shared secret) 232 | var msg = CognitoAuthHelper.CombineBytes( 233 | Encoding.UTF8.GetBytes(deviceGroupKey), 234 | Encoding.UTF8.GetBytes(deviceKey), 235 | secretBlockBytes, 236 | Encoding.UTF8.GetBytes(formattedTimestamp) 237 | ); 238 | 239 | using (var hashAlgorithm = new HMACSHA256(key)) 240 | { 241 | return hashAlgorithm.ComputeHash(msg); 242 | } 243 | } 244 | 245 | /// 246 | /// Creates the Device Password Authentication Key based on the SRP protocol 247 | /// 248 | /// Username of Cognito User 249 | /// Password of CognitoDevice 250 | /// GroupKey of CognitoDevice 251 | /// Returned from TupleAa 252 | /// BigInteger SRPB from AWS ChallengeParameters 253 | /// BigInteger salt from AWS ChallengeParameters 254 | /// Returns the password authentication key for the SRP protocol 255 | public static byte[] GetDeviceAuthenticationKey( 256 | string username, 257 | string devicePass, 258 | string deviceGroup, 259 | Tuple Aa, 260 | BigInteger B, 261 | BigInteger salt) 262 | { 263 | // Authenticate the password 264 | // u = H(A, B) 265 | byte[] contentBytes = CognitoAuthHelper.CombineBytes(Aa.Item1.ToBigEndianByteArray(), B.ToBigEndianByteArray()); 266 | byte[] digest = CognitoAuthHelper.Sha256.ComputeHash(contentBytes); 267 | 268 | BigInteger u = BigIntegerExtensions.FromUnsignedBigEndian(digest); 269 | if (u.Equals(BigInteger.Zero)) 270 | { 271 | throw new ArgumentException("Hash of A and B cannot be zero."); 272 | } 273 | 274 | // x = H(salt | H(deviceGroupKey | deviceKey | ":" | devicePassword)) 275 | byte[] deviceContent = CognitoAuthHelper.CombineBytes(Encoding.UTF8.GetBytes(deviceGroup), Encoding.UTF8.GetBytes(username), 276 | Encoding.UTF8.GetBytes(":"), Encoding.UTF8.GetBytes(devicePass)); 277 | byte[] deviceHash = CognitoAuthHelper.Sha256.ComputeHash(deviceContent); 278 | byte[] xBytes = CognitoAuthHelper.CombineBytes(salt.ToBigEndianByteArray(), deviceHash); 279 | 280 | byte[] xDigest = CognitoAuthHelper.Sha256.ComputeHash(xBytes); 281 | BigInteger x = BigIntegerExtensions.FromUnsignedBigEndian(xDigest); 282 | 283 | var gX = BigInteger.ModPow(g, x, N); 284 | // Use HKDF to get final password authentication key 285 | var intValue2 = (B - k * gX).TrueMod(N); 286 | var s_value = BigInteger.ModPow(intValue2, Aa.Item2 + u * x, N); 287 | 288 | HkdfSha256 hkdfSha256 = new HkdfSha256(u.ToBigEndianByteArray(), s_value.ToBigEndianByteArray()); 289 | return hkdfSha256.Expand(Encoding.UTF8.GetBytes(DerivedKeyInfo), DerivedKeySizeBytes); 290 | } 291 | 292 | /// 293 | /// Create a cryptographically secure random BigInteger 294 | /// 295 | /// 296 | public static BigInteger CreateBigIntegerRandom() 297 | { 298 | var b = new byte[SecretKeySizeBytes]; 299 | using(var cryptoRandom = RandomNumberGenerator.Create()) 300 | { 301 | cryptoRandom.GetBytes(b); 302 | } 303 | return BigIntegerExtensions.FromUnsignedBigEndian(b); 304 | } 305 | } 306 | } -------------------------------------------------------------------------------- /src/Amazon.Extensions.CognitoAuthentication/Util/BigIntegerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Numerics; 4 | 5 | namespace Amazon.Extensions.CognitoAuthentication.Util 6 | { 7 | internal static class BigIntegerExtensions 8 | { 9 | /// 10 | /// Turn a hex string into a BigInteger assuming it's an unsigned, little endian hex string. 11 | /// 12 | /// 13 | /// 14 | public static BigInteger FromUnsignedLittleEndianHex(string hex) => BigInteger.Parse("0" + hex, NumberStyles.HexNumber); 15 | 16 | /// 17 | /// If the sign of the remainder of self % other is < 0 then add other so that the answer is always positive. 18 | /// 19 | /// 20 | /// 21 | /// 22 | public static BigInteger TrueMod(this BigInteger self, BigInteger other) 23 | { 24 | var remainder = self % other; 25 | return remainder.Sign >= 0 ? remainder : remainder + other; 26 | } 27 | 28 | /// 29 | /// Return a big endian byte array that's equivalent to this BigInteger. 30 | /// 31 | /// 32 | /// 33 | public static byte[] ToBigEndianByteArray(this BigInteger self) => self.ToByteArray().Reverse(); 34 | 35 | /// 36 | /// Turn a byte array into a BigInteger, assuming it's an unsigned big endian integer. 37 | /// 38 | /// 39 | /// 40 | public static BigInteger FromUnsignedBigEndian(byte[] bytes) 41 | { 42 | var reverse = bytes.Reverse(); 43 | 44 | //Need to end with a zero byte to force positive 45 | var ensurePos = new byte[reverse.Length + 1]; 46 | Array.Copy(reverse, ensurePos, reverse.Length); 47 | 48 | return new BigInteger(ensurePos); 49 | } 50 | 51 | private static T[] Reverse(this T[] array) 52 | { 53 | var reverse = new T[array.Length]; 54 | for(int rev = array.Length - 1, index = 0; rev >= 0; rev--, index++) 55 | { 56 | reverse[index] = array[rev]; 57 | } 58 | return reverse; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Amazon.Extensions.CognitoAuthentication/Util/CognitoAuthHelper.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | using System; 17 | using System.Text; 18 | using System.Collections.Generic; 19 | using System.Security.Cryptography; 20 | using System.Reflection; 21 | using Amazon.Runtime; 22 | using Amazon.CognitoIdentityProvider.Model; 23 | 24 | namespace Amazon.Extensions.CognitoAuthentication.Util 25 | { 26 | internal static class CognitoAuthHelper 27 | { 28 | [ThreadStatic] private static SHA256 sha256 = null; 29 | 30 | private static readonly string _assemblyVersion = GetAssemblyFileVersion(); 31 | 32 | private static readonly string _userAgentSuffix = $"lib/AWSDotNetCognito#{_assemblyVersion}"; 33 | /// 34 | /// Property to access the thread-safe SHA256 member variable. 35 | /// Creates a SHA256 instance if one does not exist. 36 | /// 37 | internal static SHA256 Sha256 38 | { 39 | get 40 | { 41 | if (sha256 == null) 42 | { 43 | sha256 = SHA256.Create(); 44 | } 45 | 46 | return sha256; 47 | } 48 | } 49 | 50 | /// 51 | /// Computes the secret hash for the user pool using the corresponding userID, clientID, 52 | /// and client secret 53 | /// 54 | /// The current userID 55 | /// The clientID for the client being used 56 | /// The client secret of the corresponding clientID 57 | /// Returns the secret hash for the user pool using the corresponding 58 | /// userID, clientID, and client secret 59 | public static string GetUserPoolSecretHash(string userID, string clientID, string clientSecret) 60 | { 61 | string message = userID + clientID; 62 | byte[] keyBytes = Encoding.UTF8.GetBytes(clientSecret); 63 | byte[] messageBytes = Encoding.UTF8.GetBytes(message); 64 | 65 | HMACSHA256 hmacSha256 = new HMACSHA256(keyBytes); 66 | byte[] hashMessage = hmacSha256.ComputeHash(messageBytes); 67 | 68 | return Convert.ToBase64String(hashMessage); 69 | } 70 | 71 | /// 72 | /// Creates a byte array which combines all of the byte[] in values in the order of the array 73 | /// 74 | /// An array of byte[] to be combined 75 | /// Returns a byte array which combines all of the byte[] in values 76 | internal static byte[] CombineBytes(params byte[][] values) 77 | { 78 | int combinedLength = 0; 79 | byte[] returnBytes; 80 | int copyIndex = 0; 81 | 82 | for (int i = 0; i < values.Length; i++) 83 | { 84 | combinedLength += values[i].Length; 85 | } 86 | 87 | returnBytes = new byte[combinedLength]; 88 | 89 | for (int i = 0; i < values.Length; i++) 90 | { 91 | Buffer.BlockCopy(values[i], 0, returnBytes, copyIndex, values[i].Length); 92 | copyIndex += values[i].Length; 93 | } 94 | 95 | return returnBytes; 96 | } 97 | 98 | /// 99 | /// Converts a hexadecimal string to a byte array 100 | /// 101 | /// The hexadecimal string to be converted to a byte array 102 | /// Returns the byte array for the corresponding string 103 | internal static byte[] StringToByteArray(string hexString) 104 | { 105 | if (hexString.Length % 2 != 0) 106 | { 107 | throw new ArgumentException("Malformed hexString.", nameof(hexString)); 108 | } 109 | 110 | int stringLen = hexString.Length; 111 | byte[] bytes = new byte[stringLen / 2]; 112 | 113 | for (int i = 0; i < stringLen; i += 2) 114 | { 115 | bytes[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16); 116 | } 117 | 118 | return bytes; 119 | } 120 | 121 | /// 122 | /// Internal method to convert a dictionary of user attributes to a list of AttributeType 123 | /// 124 | /// Dictionary containing attributes of type string 125 | /// Returns a List of AttributeType objects 126 | internal static List CreateAttributeList(IDictionary attributeDict) 127 | { 128 | List attributeList = new List(); 129 | foreach (KeyValuePair data in attributeDict ?? new Dictionary()) 130 | { 131 | AttributeType attribute = new AttributeType() 132 | { 133 | Name = data.Key, 134 | Value = data.Value 135 | }; 136 | 137 | attributeList.Add(attribute); 138 | } 139 | 140 | return attributeList; 141 | } 142 | 143 | /// 144 | /// Internal method for handling metrics 145 | /// 146 | private const string UserAgentHeader = "User-Agent"; 147 | 148 | internal static void ServiceClientBeforeRequestEvent(object sender, RequestEventArgs e) 149 | { 150 | WebServiceRequestEventArgs args = e as WebServiceRequestEventArgs; 151 | if (args == null || !args.Headers.ContainsKey(UserAgentHeader) || args.Headers[UserAgentHeader].Contains(_userAgentSuffix)) 152 | return; 153 | 154 | args.Headers[UserAgentHeader] = args.Headers[UserAgentHeader] + " " + _userAgentSuffix; 155 | } 156 | 157 | private static string GetAssemblyFileVersion() 158 | { 159 | var assembly = typeof(CognitoAuthHelper).GetTypeInfo().Assembly; 160 | AssemblyFileVersionAttribute attribute = assembly.GetCustomAttribute(typeof(AssemblyFileVersionAttribute)) as AssemblyFileVersionAttribute; 161 | 162 | var version = attribute == null ? "Unknown" : attribute.Version; 163 | return version; 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/Amazon.Extensions.CognitoAuthentication/Util/CognitoConstants.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | namespace Amazon.Extensions.CognitoAuthentication.Util 17 | { 18 | internal class CognitoConstants 19 | { 20 | // Challenge Parameters 21 | public static readonly string ChlgParamSrpA = "SRP_A"; 22 | public static readonly string ChlgParamSrpB = "SRP_B"; 23 | public static readonly string ChlgParamSecretHash = "SECRET_HASH"; 24 | public static readonly string ChlgParamUsername = "USERNAME"; 25 | public static readonly string ChlgParamChallengeName = "CHALLENGE_NAME"; 26 | public static readonly string ChlgParamSalt = "SALT"; 27 | public static readonly string ChlgParamSecretBlock = "SECRET_BLOCK"; 28 | public static readonly string ChlgParamUserIDSrp = "USER_ID_FOR_SRP"; 29 | public static readonly string ChlgParamRefreshToken = "REFRESH_TOKEN"; 30 | 31 | public static readonly string ChlgParamPassSecretBlock = "PASSWORD_CLAIM_SECRET_BLOCK"; 32 | public static readonly string ChlgParamPassSignature = "PASSWORD_CLAIM_SIGNATURE"; 33 | public static readonly string ChlgParamTimestamp = "TIMESTAMP"; 34 | public static readonly string ChlgParamDeliveryDest = "CODE_DELIVERY_DESTINATION"; 35 | public static readonly string ChlgParamDeliveryMed = "CODE_DELIVERY_DELIVERY_MEDIUM"; 36 | 37 | public static readonly string ChlgParamSmsMfaCode = "SMS_MFA_CODE"; 38 | public static readonly string ChlgParamSoftwareTokenMfaCode = "SOFTWARE_TOKEN_MFA_CODE"; 39 | public static readonly string ChlgParamEmailMfaCode = "EMAIL_OTP_CODE"; 40 | public static readonly string ChlgParamDeviceKey = "DEVICE_KEY"; 41 | 42 | public static readonly string ChlgParamUserAttrs = "userAttributes"; 43 | public static readonly string ChlgParamRequiredAttrs = "requiredAttributes"; 44 | public static readonly string ChlgParamUserAttrPrefix = "userAttributes."; 45 | public static readonly string ChlgParamNewPassword = "NEW_PASSWORD"; 46 | public static readonly string ChlgParamPassword = "PASSWORD"; 47 | 48 | // User Attributes 49 | public static readonly string UserAttrEmail = "email"; 50 | public static readonly string UserAttrPhoneNumber = "phone_number"; 51 | 52 | // Device Attributes 53 | public static readonly string DeviceAttrName = "device_name"; 54 | public static readonly string DeviceAttrRemembered = "remembered"; 55 | public static readonly string DeviceAttrNotRemembered = "not_remembered"; 56 | 57 | public static readonly string DeviceChlgParamSalt = "salt"; 58 | public static readonly string DeviceChlgParamVerifier = "verifier"; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Amazon.Extensions.CognitoAuthentication/Util/CognitoDeviceHelper.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | using System; 17 | using System.Collections.Generic; 18 | using System.Numerics; 19 | using System.Security.Cryptography; 20 | using System.Text; 21 | 22 | namespace Amazon.Extensions.CognitoAuthentication.Util 23 | { 24 | /// 25 | /// Class that helps with device SRP authentication 26 | /// 27 | internal static class CognitoDeviceHelper 28 | { 29 | public static byte[] Salt { get; set; } 30 | public static BigInteger Verifier { get; set; } 31 | 32 | /// 33 | /// Generates the salt, verifier, and secret verification parameters for device SRP 34 | /// 35 | /// The device key for the associated CognitoDevice 36 | /// The device group key for the associated CognitoDevice 37 | /// Returns a dictionary with key-value pairings for the salt, verifier, and secret for 38 | /// the associated CognitoDevice 39 | public static IDictionary GenerateVerificationParameters(string deviceKey, string deviceGroup) 40 | { 41 | string deviceSecret = GenerateRandomString(); 42 | GenerateDeviceSaltAndVerifier(deviceGroup, deviceKey, deviceSecret); 43 | 44 | byte[] srpVerifier = Verifier.ToByteArray(); 45 | 46 | Dictionary verificationParams = new Dictionary() 47 | { 48 | { "salt", Convert.ToBase64String(Salt) }, 49 | { "verifier", Convert.ToBase64String(srpVerifier) }, 50 | { "secret", deviceSecret } 51 | }; 52 | 53 | return verificationParams; 54 | } 55 | 56 | /// 57 | /// Generates the device salt and verifier values and stores them in the appropriate 58 | /// CognitoDeviceHelper properties 59 | /// 60 | /// The device group key for the associated CognitoDevice 61 | /// The device key for the CognitoDevice 62 | /// The password for the CognitoUser associated with the device 63 | public static void GenerateDeviceSaltAndVerifier(string deviceGroupKey, string deviceKey, string password) 64 | { 65 | byte[] deviceKeyHash = GetDeviceKeyHash(deviceGroupKey, deviceKey, password); 66 | 67 | Salt = new byte[16]; 68 | using (var cryptoRandom = RandomNumberGenerator.Create()) 69 | { 70 | cryptoRandom.GetBytes(Salt); 71 | } 72 | Verifier = CalculateVerifier(Salt, deviceKeyHash); 73 | } 74 | 75 | /// 76 | /// Calculates the device verifier for the device given the salt and device key hash 77 | /// 78 | /// The salt for the SHA256 hash to compute the device verifier 79 | /// The device key hash for the associated CognitoDevice 80 | /// Returns the device verifier for the associated CognitoDevice 81 | public static BigInteger CalculateVerifier(byte[] salt, byte[] deviceKeyHash) 82 | { 83 | byte[] contentBytes = CognitoAuthHelper.CombineBytes(salt, deviceKeyHash); 84 | byte[] digest = CognitoAuthHelper.Sha256.ComputeHash(contentBytes); 85 | 86 | BigInteger x = new BigInteger(digest); 87 | return BigInteger.ModPow(x, AuthenticationHelper.g, AuthenticationHelper.N); 88 | } 89 | 90 | /// 91 | /// Computes the device key hash given the device group key, device key, and user password 92 | /// 93 | /// The device group key for the CognitoDevice 94 | /// The device key for the CognitoDevice 95 | /// The password for the CognitoUser associated with the device 96 | /// Returns the device key hash for the given device 97 | public static byte[] GetDeviceKeyHash(string deviceGroupKey, string deviceKey, string password) 98 | { 99 | byte[] contentBytes = CognitoAuthHelper.CombineBytes(Encoding.UTF8.GetBytes(deviceGroupKey), 100 | Encoding.UTF8.GetBytes(deviceKey), Encoding.UTF8.GetBytes(":"), Encoding.UTF8.GetBytes(password)); 101 | 102 | return CognitoAuthHelper.Sha256.ComputeHash(contentBytes); 103 | } 104 | 105 | /// 106 | /// Generates a random string using a globally unique identifier 107 | /// 108 | /// Returns a random string 109 | public static string GenerateRandomString() 110 | { 111 | return Guid.NewGuid().ToString(); 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /src/Amazon.Extensions.CognitoAuthentication/Util/HkdfSha256.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | using Amazon.Extensions.CognitoAuthentication.Util; 17 | using System; 18 | using System.Security.Cryptography; 19 | 20 | namespace Amazon.Extensions.CognitoAuthentication 21 | { 22 | /// 23 | /// Class used to carry out the HKDF protocol 24 | /// Adapted from https://tools.ietf.org/html/rfc5869 25 | /// 26 | internal class HkdfSha256 27 | { 28 | internal const int HLen = 32; 29 | 30 | /// 31 | /// The pseudorandom key for the HKDF protocol. Prk is generated in the 32 | /// constructor of Hkdf and once set cannot be changed. 33 | /// 34 | internal byte[] Prk { get; private set; } 35 | 36 | /// 37 | /// The HMACSHA256 hashing algorithm object to compute hashes while carrying out 38 | /// the HKDF protocol 39 | /// 40 | private HMACSHA256 HmacSha256 { get; set; } 41 | 42 | /// 43 | /// Creates an instance of Hkdf and performs the "Extract" portion of the HKDF protocol 44 | /// to generate the pseudorandom key property 45 | /// 46 | /// The salt for the extraction hash 47 | /// The input key material for the extraction hash 48 | internal HkdfSha256(byte[] salt, byte[] ikm) 49 | { 50 | HmacSha256 = new HMACSHA256(salt); 51 | 52 | Prk = HmacSha256.ComputeHash(ikm); 53 | } 54 | 55 | /// 56 | /// Performs the "Expand" portion of the Hkdf protocol to make 57 | /// the ouput key material of the desired length 58 | /// 59 | /// Contextual information for the expansion hash 60 | /// The desired length of the output key material in bytes 61 | /// Returns the output key material for the expansion protion of the HKDF protocol 62 | internal byte[] Expand(byte[] info, int length) 63 | { 64 | if (length > HLen * 255) 65 | { 66 | throw new ArgumentException("Length must be <= " + HLen * 255); 67 | } 68 | 69 | byte[] outputKeyMaterial = new byte[length]; 70 | HmacSha256 = new HMACSHA256(Prk); 71 | 72 | byte currentByte = 1; 73 | byte[] hashedBlock = new byte[0]; 74 | byte[] currentBlock; 75 | int bytesRemaining = length; 76 | 77 | while(bytesRemaining > 0) 78 | { 79 | currentBlock = CognitoAuthHelper.CombineBytes(hashedBlock, info, new byte[] { currentByte }); 80 | hashedBlock = HmacSha256.ComputeHash(currentBlock); 81 | 82 | Buffer.BlockCopy(hashedBlock, 0, outputKeyMaterial, length-bytesRemaining, Math.Min(hashedBlock.Length, bytesRemaining)); 83 | bytesRemaining -= hashedBlock.Length; 84 | currentByte++; 85 | } 86 | 87 | return outputKeyMaterial; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /test/Amazon.Extensions.CognitoAuthentication.IntegrationTests/Amazon.Extensions.CognitoAuthentication.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /test/Amazon.Extensions.CognitoAuthentication.IntegrationTests/AuthenticationConfirmUserTests.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | using System.Threading.Tasks; 17 | using System.Collections.Generic; 18 | using Xunit; 19 | 20 | using Amazon.CognitoIdentityProvider.Model; 21 | using Amazon.Extensions.CognitoAuthentication.Util; 22 | 23 | namespace Amazon.Extensions.CognitoAuthentication.IntegrationTests 24 | { 25 | public class AuthenticationConfirmUserTests : BaseAuthenticationTestClass 26 | { 27 | public AuthenticationConfirmUserTests() : base() 28 | { 29 | SignUpRequest signUpRequest = new SignUpRequest() 30 | { 31 | ClientId = pool.ClientID, 32 | Password = "PassWord1!", 33 | Username = "User5", 34 | UserAttributes = new List() 35 | { 36 | new AttributeType() {Name=CognitoConstants.UserAttrEmail, Value="xxx@yyy.zzz"}, 37 | }, 38 | ValidationData = new List() 39 | { 40 | new AttributeType() {Name=CognitoConstants.UserAttrEmail, Value="xxx@yyy.zzz"} 41 | } 42 | }; 43 | 44 | SignUpResponse signUpResponse = provider.SignUpAsync(signUpRequest).Result; 45 | 46 | AdminConfirmSignUpRequest confirmRequest = new AdminConfirmSignUpRequest() 47 | { 48 | Username = "User5", 49 | UserPoolId = pool.PoolID 50 | }; 51 | AdminConfirmSignUpResponse confirmResponse = provider.AdminConfirmSignUpAsync(confirmRequest).Result; 52 | user = new CognitoUser("User5", pool.ClientID, pool, provider); 53 | } 54 | 55 | //Tests SRP authentication flow for web applications 56 | [Fact] 57 | public async Task TestGenericSrpAuthentication() 58 | { 59 | string password = "PassWord1!"; 60 | 61 | AuthFlowResponse context = 62 | await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest() 63 | { 64 | Password = password 65 | }); 66 | 67 | Assert.True(user.SessionTokens.IsValid()); 68 | } 69 | 70 | // Tests the DeleteUser method 71 | [Fact] 72 | public async Task TestDeleteUser() 73 | { 74 | string userID = user.UserID; 75 | List users = new List(); 76 | 77 | AuthFlowResponse context = 78 | await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest() 79 | { 80 | Password = "PassWord1!" 81 | }); 82 | 83 | ListUsersRequest listUsersRequest = new ListUsersRequest() 84 | { 85 | Limit = 60, 86 | UserPoolId = pool.PoolID 87 | }; 88 | ListUsersResponse listUsersReponse = await provider.ListUsersAsync(listUsersRequest); 89 | foreach (UserType listUser in listUsersReponse.Users) 90 | { 91 | users.Add(listUser.Username); 92 | } 93 | 94 | Assert.Contains(userID, users); 95 | 96 | await user.DeleteUserAsync(); 97 | 98 | listUsersReponse = await provider.ListUsersAsync(listUsersRequest); 99 | users.Clear(); 100 | foreach(UserType listUser in listUsersReponse.Users) 101 | { 102 | users.Add(listUser.Username); 103 | } 104 | 105 | Assert.DoesNotContain(userID, users); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /test/Amazon.Extensions.CognitoAuthentication.IntegrationTests/AuthenticationCreateUserTests.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | using System.Threading.Tasks; 17 | using System.Collections.Generic; 18 | using Xunit; 19 | 20 | using Amazon.CognitoIdentityProvider; 21 | using Amazon.CognitoIdentityProvider.Model; 22 | using Amazon.Extensions.CognitoAuthentication.Util; 23 | 24 | namespace Amazon.Extensions.CognitoAuthentication.IntegrationTests 25 | { 26 | public class AuthenticationCreateUserTests : BaseAuthenticationTestClass 27 | { 28 | public AuthenticationCreateUserTests() : base() 29 | { 30 | AdminCreateUserRequest createUserRequest = new AdminCreateUserRequest() 31 | { 32 | TemporaryPassword = "PassWord1!", 33 | Username = "User5", 34 | UserAttributes = new List() 35 | { 36 | new AttributeType() {Name=CognitoConstants.UserAttrEmail, Value="xxx@yyy.zzz"}, 37 | }, 38 | ValidationData = new List() 39 | { 40 | new AttributeType() {Name=CognitoConstants.UserAttrEmail, Value="xxx@yyy.zzz"} 41 | }, 42 | UserPoolId = pool.PoolID 43 | }; 44 | 45 | AdminCreateUserResponse createReponse = provider.AdminCreateUserAsync(createUserRequest).Result; 46 | 47 | user = new CognitoUser("User5", pool.ClientID, pool, provider); 48 | } 49 | 50 | // Tests the sessionauthentication process with a NEW_PASSWORD_REQURIED flow 51 | [Fact] 52 | public async Task TestNewPasswordRequiredFlow() 53 | { 54 | string password = "PassWord1!"; 55 | 56 | AuthFlowResponse context = 57 | await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest() 58 | { 59 | Password = password 60 | }); 61 | 62 | Assert.Equal(context.ChallengeName, ChallengeNameType.NEW_PASSWORD_REQUIRED); 63 | 64 | context = await user.RespondToNewPasswordRequiredAsync(new RespondToNewPasswordRequiredRequest() 65 | { 66 | SessionID = context.SessionID, 67 | NewPassword = "NewPassword1!" 68 | }); 69 | 70 | Assert.True(user.SessionTokens.IsValid()); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /test/Amazon.Extensions.CognitoAuthentication.IntegrationTests/AuthenticationSignUpUserTests.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | using System; 17 | using System.Threading.Tasks; 18 | using System.Collections.Generic; 19 | using Xunit; 20 | 21 | using Amazon.CognitoIdentityProvider.Model; 22 | using Amazon.Extensions.CognitoAuthentication.Util; 23 | 24 | namespace Amazon.Extensions.CognitoAuthentication.IntegrationTests 25 | { 26 | public class AuthenticationSignUpUserTests : BaseAuthenticationTestClass 27 | { 28 | public AuthenticationSignUpUserTests() : base() 29 | { 30 | SignUpRequest signUpRequest = new SignUpRequest() 31 | { 32 | ClientId = pool.ClientID, 33 | Password = "PassWord1!", 34 | Username = "User5", 35 | UserAttributes = new List() 36 | { 37 | new AttributeType() {Name=CognitoConstants.UserAttrEmail, Value="xxx@yyy.zzz"}, 38 | }, 39 | ValidationData = new List() 40 | { 41 | new AttributeType() {Name=CognitoConstants.UserAttrEmail, Value="xxx@yyy.zzz"} 42 | } 43 | }; 44 | 45 | SignUpResponse signUpResponse = provider.SignUpAsync(signUpRequest).Result; 46 | user = new CognitoUser("User5", pool.ClientID, pool, provider); 47 | } 48 | 49 | // Tests the SignUp method (using random, dummy email) 50 | [Fact] 51 | public async Task TestSignUpProcess() 52 | { 53 | string userID = "User55"; 54 | string password = "PassWord1!"; 55 | Dictionary userAttributes = new Dictionary(StringComparer.Ordinal) 56 | { 57 | { CognitoConstants.UserAttrEmail, "xxx@yyy.zzz"} 58 | }; 59 | Dictionary validationData = new Dictionary(StringComparer.Ordinal) 60 | { 61 | { CognitoConstants.UserAttrEmail, "xxx@yyy.zzz"} 62 | }; 63 | 64 | await pool.SignUpAsync(userID, password, userAttributes, validationData); 65 | 66 | ListUsersRequest listUsersRequest = new ListUsersRequest() 67 | { 68 | Limit = 2, 69 | UserPoolId = pool.PoolID 70 | }; 71 | ListUsersResponse listUsersResponse = await provider.ListUsersAsync(listUsersRequest); 72 | bool containsUser55 = false; 73 | 74 | foreach (UserType user in listUsersResponse.Users) 75 | { 76 | if (string.Equals(user.Username, userID, StringComparison.Ordinal)) 77 | { 78 | containsUser55 = true; 79 | } 80 | } 81 | 82 | Assert.True(containsUser55); 83 | } 84 | 85 | // Tests that ConfirmSignUp reaches the proper failure point with incorrect confirmation code 86 | [Fact] 87 | public async Task TestConfirmSignUpFail() 88 | { 89 | await Assert.ThrowsAsync(() => user.ConfirmSignUpAsync("fakeConfirmationCode", false)); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /test/Amazon.Extensions.CognitoAuthentication.IntegrationTests/BaseAuthenticationTestClass.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | using System; 17 | using System.Collections.Generic; 18 | 19 | using Amazon.CognitoIdentityProvider; 20 | using Amazon.CognitoIdentityProvider.Model; 21 | using Amazon.Extensions.CognitoAuthentication.Util; 22 | 23 | namespace Amazon.Extensions.CognitoAuthentication.IntegrationTests 24 | { 25 | /// 26 | /// Base class to be used for authentication integrations tests 27 | /// Allows for child classes to create, sign up, or confirm users 28 | /// 29 | public partial class BaseAuthenticationTestClass : IDisposable 30 | { 31 | protected AmazonCognitoIdentityProviderClient provider; 32 | protected CognitoUserPool pool; 33 | protected CognitoUser user; 34 | 35 | static BaseAuthenticationTestClass() 36 | { 37 | AWSConfigs.RegionEndpoint = RegionEndpoint.USEast1; 38 | } 39 | 40 | public BaseAuthenticationTestClass() 41 | { 42 | UserPoolPolicyType passwordPolicy = new UserPoolPolicyType(); 43 | List requiredAttributes = new List(); 44 | List verifiedAttributes = new List(); 45 | 46 | provider = new AmazonCognitoIdentityProviderClient(); 47 | 48 | AdminCreateUserConfigType adminCreateUser = new AdminCreateUserConfigType() 49 | { 50 | UnusedAccountValidityDays = 8, 51 | AllowAdminCreateUserOnly = false 52 | }; 53 | 54 | passwordPolicy.PasswordPolicy = new PasswordPolicyType() 55 | { 56 | MinimumLength = 8, 57 | RequireNumbers = true, 58 | RequireSymbols = true, 59 | RequireUppercase = true, 60 | RequireLowercase = true 61 | }; 62 | 63 | SchemaAttributeType tempSchema = new SchemaAttributeType() 64 | { 65 | Required = true, 66 | Name = CognitoConstants.UserAttrEmail, 67 | AttributeDataType = AttributeDataType.String 68 | }; 69 | requiredAttributes.Add(tempSchema); 70 | verifiedAttributes.Add(CognitoConstants.UserAttrEmail); 71 | 72 | CreateUserPoolRequest createPoolRequest = new CreateUserPoolRequest 73 | { 74 | PoolName = "testPool_" + DateTime.UtcNow.ToString("yyyyMMdd_HHmmss"), 75 | Policies = passwordPolicy, 76 | Schema = requiredAttributes, 77 | AdminCreateUserConfig = adminCreateUser, 78 | MfaConfiguration = "OFF", 79 | AutoVerifiedAttributes = verifiedAttributes, 80 | DeviceConfiguration = new DeviceConfigurationType() 81 | { 82 | ChallengeRequiredOnNewDevice = false, 83 | DeviceOnlyRememberedOnUserPrompt = false 84 | } 85 | }; 86 | CreateUserPoolResponse createPoolResponse = provider.CreateUserPoolAsync(createPoolRequest).Result; 87 | UserPoolType userPoolCreated = createPoolResponse.UserPool; 88 | 89 | CreateUserPoolClientRequest clientRequest = new CreateUserPoolClientRequest() 90 | { 91 | ClientName = "App_" + DateTime.UtcNow.ToString("yyyyMMdd_HHmmss"), 92 | UserPoolId = userPoolCreated.Id, 93 | GenerateSecret = false, 94 | 95 | }; 96 | CreateUserPoolClientResponse clientResponse = provider.CreateUserPoolClientAsync(clientRequest).Result; 97 | UserPoolClientType clientCreated = clientResponse.UserPoolClient; 98 | 99 | pool = new CognitoUserPool(userPoolCreated.Id, clientCreated.ClientId, provider, ""); 100 | } 101 | 102 | /// 103 | /// Internal method that cleans up the created user pool (along with associated client/user) 104 | /// for testing 105 | /// 106 | public virtual void Dispose() 107 | { 108 | try 109 | { 110 | provider.DeleteUserPoolAsync(new DeleteUserPoolRequest() 111 | { 112 | UserPoolId = pool.PoolID 113 | }).Wait(); 114 | 115 | provider.Dispose(); 116 | } 117 | catch (Exception ex) 118 | { 119 | System.Diagnostics.Debug.WriteLine(ex.Message); 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /test/Amazon.Extensions.CognitoAuthentication.IntegrationTests/CognitoAWSCredentialsTests.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | using System; 17 | using System.Collections.Generic; 18 | using System.Threading; 19 | using System.Threading.Tasks; 20 | using Xunit; 21 | 22 | using Amazon; 23 | using Amazon.CognitoIdentity; 24 | using Amazon.CognitoIdentityProvider.Model; 25 | using Amazon.CognitoIdentity.Model; 26 | using Amazon.Extensions.CognitoAuthentication; 27 | using Amazon.IdentityManagement; 28 | using Amazon.IdentityManagement.Model; 29 | using Amazon.Runtime; 30 | using Amazon.S3; 31 | using Amazon.S3.Model; 32 | 33 | using Amazon.Extensions.CognitoAuthentication.IntegrationTests; 34 | using Amazon.Runtime.Credentials; 35 | 36 | namespace CognitoAuthentication.IntegrationTests.NET45 37 | { 38 | public class CognitoCredentialsTests : AuthenticationConfirmUserTests 39 | { 40 | private string policyArn; 41 | private string roleName; 42 | private string identityPoolId; 43 | 44 | private AmazonCognitoIdentityClient identityClient; 45 | AWSCredentials clientCredentials = DefaultAWSCredentialsIdentityResolver.GetCredentials(); 46 | private AmazonIdentityManagementServiceClient managementClient; 47 | 48 | [Fact] 49 | //Tests GetCognitoAWSCredentials 50 | public async Task TestGetCognitoAWSCredentials() 51 | { 52 | string password = "PassWord1!"; 53 | string poolRegion = user.UserPool.PoolID.Substring(0, user.UserPool.PoolID.IndexOf("_")); 54 | string providerName = "cognito-idp." + poolRegion + ".amazonaws.com/" + user.UserPool.PoolID; 55 | 56 | AuthFlowResponse context = 57 | await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest() 58 | { 59 | Password = password, 60 | UserContextData = new UserContextDataType { 61 | EncodedData = "AmazonCognitoAdvancedSecurityData_object", 62 | IpAddress = "192.0.2.1" 63 | } 64 | }); 65 | 66 | //Create identity pool 67 | identityClient = new AmazonCognitoIdentityClient(clientCredentials, clientRegion); 68 | CreateIdentityPoolResponse poolResponse = 69 | await identityClient.CreateIdentityPoolAsync(new CreateIdentityPoolRequest() 70 | { 71 | AllowUnauthenticatedIdentities = false, 72 | CognitoIdentityProviders = new List() 73 | { 74 | new CognitoIdentityProviderInfo() { ProviderName = providerName, ClientId = user.ClientID} 75 | }, 76 | IdentityPoolName = "TestIdentityPool" + DateTime.UtcNow.ToString("yyyyMMdd_HHmmss"), 77 | 78 | }); 79 | identityPoolId = poolResponse.IdentityPoolId; 80 | 81 | //Create role for identity pool 82 | managementClient = new AmazonIdentityManagementServiceClient(clientCredentials, clientRegion); 83 | CreateRoleResponse roleResponse = await managementClient.CreateRoleAsync(new CreateRoleRequest() 84 | { 85 | RoleName = "_TestRole_" + DateTime.UtcNow.ToString("yyyyMMdd_HHmmss"), 86 | AssumeRolePolicyDocument = "{\"Version\": \"2012-10-17\",\"Statement\": [{\"Effect" + 87 | "\": \"Allow\",\"Principal\": {\"Federated\": \"cognito-identity.amazonaws.com\"}," + 88 | "\"Action\": \"sts:AssumeRoleWithWebIdentity\",\"Condition\": {\"StringEquals\": {" + 89 | "\"cognito-identity.amazonaws.com:aud\": [\"" + identityPoolId + "\"]}}}]}" 90 | }); 91 | roleName = roleResponse.Role.RoleName; 92 | 93 | //Create and attach policy for role 94 | CreatePolicyResponse policyResponse = await managementClient.CreatePolicyAsync(new CreatePolicyRequest() 95 | { 96 | PolicyDocument = "{\"Version\": \"2012-10-17\",\"Statement\": " + 97 | "[{\"Effect\": \"Allow\",\"Action\": [\"mobileanalytics:PutEvents\",\"cog" + 98 | "nito-sync:*\",\"cognito-identity:*\",\"s3:*\"],\"Resource\": [\"*\"]}]}", 99 | PolicyName = "_Cognito_" + DateTime.UtcNow.ToString("yyyyMMdd_HHmmss"), 100 | }); 101 | policyArn = policyResponse.Policy.Arn; 102 | 103 | AttachRolePolicyRequest attachRequest = new AttachRolePolicyRequest() 104 | { 105 | PolicyArn = policyArn, 106 | RoleName = roleName 107 | }; 108 | AttachRolePolicyResponse attachRolePolicyResponse = await managementClient.AttachRolePolicyAsync(attachRequest); 109 | 110 | //Set the role for the identity pool 111 | await identityClient.SetIdentityPoolRolesAsync(new SetIdentityPoolRolesRequest() 112 | { 113 | IdentityPoolId = identityPoolId, 114 | Roles = new Dictionary() 115 | { 116 | { "authenticated", roleResponse.Role.Arn }, 117 | { "unauthenticated", roleResponse.Role.Arn } 118 | }, 119 | }); 120 | 121 | //Create and test credentials 122 | CognitoAWSCredentials credentials = user.GetCognitoAWSCredentials(identityPoolId, clientRegion); 123 | 124 | using (var client = new AmazonS3Client(credentials, Amazon.RegionEndpoint.USEast1)) 125 | { 126 | ListBucketsResponse bucketsResponse = null; 127 | 128 | for (var tries = 0; tries < 5; tries++) 129 | { 130 | try 131 | { 132 | bucketsResponse = await client.ListBucketsAsync(new ListBucketsRequest()); 133 | break; 134 | } 135 | catch (Exception) 136 | { 137 | Thread.Sleep(5000); 138 | } 139 | } 140 | 141 | Assert.True(null != bucketsResponse, "Failed to list buckets after 5 tries"); 142 | Assert.Equal(System.Net.HttpStatusCode.OK, bucketsResponse.HttpStatusCode); 143 | } 144 | } 145 | 146 | /// 147 | /// Internal method that cleans up the created identity pool (along with associated 148 | /// clients/roles) for testing 149 | /// 150 | public override void Dispose() 151 | { 152 | try 153 | { 154 | identityClient.DeleteIdentityPoolAsync(new DeleteIdentityPoolRequest() 155 | { 156 | IdentityPoolId = identityPoolId 157 | }).GetAwaiter().GetResult(); 158 | 159 | managementClient.DetachRolePolicyAsync(new DetachRolePolicyRequest() 160 | { 161 | PolicyArn = policyArn, 162 | RoleName = roleName 163 | }).GetAwaiter().GetResult(); 164 | 165 | managementClient.DeletePolicyAsync(new DeletePolicyRequest() 166 | { 167 | PolicyArn = policyArn 168 | }).GetAwaiter().GetResult(); 169 | 170 | managementClient.DeleteRoleAsync(new DeleteRoleRequest() 171 | { 172 | RoleName = roleName 173 | }).GetAwaiter().GetResult(); 174 | 175 | identityClient.Dispose(); 176 | managementClient.Dispose(); 177 | } 178 | catch (Exception ex) 179 | { 180 | System.Diagnostics.Debug.WriteLine(ex.Message); 181 | } 182 | 183 | base.Dispose(); 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /test/Amazon.Extensions.CognitoAuthentication.IntegrationTests/MfaAuthenticationTests.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | using System; 17 | using System.Collections.Generic; 18 | using System.Threading.Tasks; 19 | using Xunit; 20 | 21 | using Amazon.Runtime; 22 | using Amazon.CognitoIdentityProvider.Model; 23 | using Amazon.CognitoIdentityProvider; 24 | using Amazon.Extensions.CognitoAuthentication.Util; 25 | using Amazon.IdentityManagement; 26 | using Amazon.IdentityManagement.Model; 27 | using Amazon.Runtime.Credentials; 28 | 29 | namespace Amazon.Extensions.CognitoAuthentication.IntegrationTests 30 | { 31 | public class MfaAuthenticationTests : BaseAuthenticationTestClass 32 | { 33 | private string policyArn; 34 | private string policyName; 35 | private string roleArn; 36 | private string roleName; 37 | 38 | //Tests MFA authentication flow 39 | [Fact] 40 | public async Task TestMfaAuthenticationFlow() 41 | { 42 | string password = "PassWord1!"; 43 | 44 | AuthFlowResponse context = 45 | await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest() 46 | { 47 | Password = password 48 | }); 49 | 50 | Assert.Equal(context.ChallengeName, ChallengeNameType.SMS_MFA); 51 | 52 | await Assert.ThrowsAsync(() => user.RespondToSmsMfaAuthAsync(new RespondToSmsMfaRequest() 53 | { 54 | MfaCode = "fakeMfaCode", 55 | SessionID = context.SessionID 56 | })); 57 | } 58 | 59 | /// 60 | /// Internal constructor to initialize a provider, user pool, and user for testing 61 | /// Created user info: Username = User 5, Password = PassWord1!, Email = xxx@yyy.zzz 62 | /// 63 | public MfaAuthenticationTests() 64 | { 65 | //Delete pool created by BaseAuthenticationTestClass 66 | if(pool != null) 67 | { 68 | provider.DeleteUserPoolAsync(new DeleteUserPoolRequest() 69 | { 70 | UserPoolId = pool.PoolID 71 | }).Wait(); 72 | } 73 | 74 | UserPoolPolicyType passwordPolicy = new UserPoolPolicyType(); 75 | List requiredAttributes = new List(); 76 | List verifiedAttributes = new List(); 77 | 78 | var creds = DefaultAWSCredentialsIdentityResolver.GetCredentials(); 79 | var region = FallbackRegionFactory.GetRegionEndpoint(); 80 | 81 | provider = new AmazonCognitoIdentityProviderClient(creds, region); 82 | 83 | AdminCreateUserConfigType adminCreateUser = new AdminCreateUserConfigType() 84 | { 85 | UnusedAccountValidityDays = 8, 86 | AllowAdminCreateUserOnly = false 87 | }; 88 | 89 | passwordPolicy.PasswordPolicy = new PasswordPolicyType() 90 | { 91 | MinimumLength = 8, 92 | RequireNumbers = true, 93 | RequireSymbols = true, 94 | RequireUppercase = true, 95 | RequireLowercase = true 96 | }; 97 | 98 | SchemaAttributeType emailSchema = new SchemaAttributeType() 99 | { 100 | Required = true, 101 | Name = CognitoConstants.UserAttrEmail, 102 | AttributeDataType = AttributeDataType.String 103 | }; 104 | SchemaAttributeType phoneSchema = new SchemaAttributeType() 105 | { 106 | Required = true, 107 | Name = CognitoConstants.UserAttrPhoneNumber, 108 | AttributeDataType = AttributeDataType.String 109 | }; 110 | requiredAttributes.Add(emailSchema); 111 | requiredAttributes.Add(phoneSchema); 112 | verifiedAttributes.Add(CognitoConstants.UserAttrEmail); 113 | verifiedAttributes.Add(CognitoConstants.UserAttrPhoneNumber); 114 | 115 | //Create Role for MFA 116 | using (var managementClient = new AmazonIdentityManagementServiceClient()) 117 | { 118 | CreateRoleResponse roleResponse = managementClient.CreateRoleAsync(new CreateRoleRequest() 119 | { 120 | RoleName = "TestRole_" + DateTime.UtcNow.ToString("yyyyMMdd_HHmmss"), 121 | AssumeRolePolicyDocument = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Allow" + 122 | "\",\"Principal\":{\"Service\":\"cognito-idp.amazonaws.com\"},\"Action\":\"sts:AssumeRole\",\"Condition" + 123 | "\":{\"StringEquals\":{\"sts:ExternalId\":\"8327d096-57c0-4fb7-ad24-62ea8fc692c0\"}}}]}" 124 | }).Result; 125 | 126 | roleName = roleResponse.Role.RoleName; 127 | roleArn = roleResponse.Role.Arn; 128 | 129 | //Create and attach policy for role 130 | CreatePolicyResponse createPolicyResponse = managementClient.CreatePolicyAsync(new CreatePolicyRequest() 131 | { 132 | PolicyDocument = "{\"Version\": \"2012-10-17\",\"Statement\": [{\"Effect\": \"Allow\",\"Action" + 133 | "\": [\"sns:publish\"],\"Resource\": [\"*\"]}]}", 134 | PolicyName = "Cognito_" + DateTime.UtcNow.ToString("yyyyMMdd_HHmmss"), 135 | }).Result; 136 | 137 | policyName = createPolicyResponse.Policy.PolicyName; 138 | policyArn = createPolicyResponse.Policy.Arn; 139 | 140 | managementClient.AttachRolePolicyAsync(new AttachRolePolicyRequest() 141 | { 142 | PolicyArn = policyArn, 143 | RoleName = roleName 144 | }).Wait(); 145 | } 146 | 147 | //Create user pool and client 148 | CreateUserPoolRequest createPoolRequest = new CreateUserPoolRequest 149 | { 150 | PoolName = "mfaTestPool_" + DateTime.UtcNow.ToString("yyyyMMdd_HHmmss"), 151 | Policies = passwordPolicy, 152 | Schema = requiredAttributes, 153 | AdminCreateUserConfig = adminCreateUser, 154 | MfaConfiguration = "ON", 155 | AutoVerifiedAttributes = verifiedAttributes, 156 | SmsConfiguration = new SmsConfigurationType 157 | { 158 | SnsCallerArn = roleArn, 159 | ExternalId = "8327d096-57c0-4fb7-ad24-62ea8fc692c0" 160 | } 161 | }; 162 | 163 | //Build in buffer time for role/policy to be created 164 | CreateUserPoolResponse createPoolResponse = null; 165 | string bufferExMsg = "Role does not have a trust relationship allowing Cognito to assume the role"; 166 | string bufferExMsgSNSPublish = "Role does not have permission to publish with SNS"; 167 | while (true) 168 | { 169 | try 170 | { 171 | createPoolResponse = provider.CreateUserPoolAsync(createPoolRequest).Result; 172 | break; 173 | } 174 | catch (Exception ex) when (ex.InnerException != null && (bufferExMsg.Equals(ex.InnerException.Message) || bufferExMsgSNSPublish.Equals(ex.InnerException.Message))) 175 | { 176 | System.Threading.Thread.Sleep(3000); 177 | } 178 | } 179 | 180 | UserPoolType poolCreated = createPoolResponse.UserPool; 181 | 182 | CreateUserPoolClientResponse clientResponse = 183 | provider.CreateUserPoolClientAsync(new CreateUserPoolClientRequest() 184 | { 185 | ClientName = "App1", 186 | UserPoolId = poolCreated.Id, 187 | GenerateSecret = false, 188 | }).Result; 189 | 190 | UserPoolClientType clientCreated = clientResponse.UserPoolClient; 191 | this.pool = new CognitoUserPool(poolCreated.Id, clientCreated.ClientId, provider, ""); 192 | 193 | SignUpRequest signUpRequest = new SignUpRequest() 194 | { 195 | ClientId = clientCreated.ClientId, 196 | Password = "PassWord1!", 197 | Username = "User5", 198 | UserAttributes = new List() 199 | { 200 | new AttributeType() {Name=CognitoConstants.UserAttrEmail, Value="xxx@yyy.zzz"}, 201 | new AttributeType() {Name=CognitoConstants.UserAttrPhoneNumber, Value="+15555555555"} 202 | }, 203 | ValidationData = new List() 204 | { 205 | new AttributeType() {Name=CognitoConstants.UserAttrEmail, Value="xxx@yyy.zzz"}, 206 | new AttributeType() {Name=CognitoConstants.UserAttrPhoneNumber, Value="+15555555555"} 207 | } 208 | }; 209 | 210 | SignUpResponse signUpResponse = provider.SignUpAsync(signUpRequest).Result; 211 | 212 | AdminConfirmSignUpRequest confirmRequest = new AdminConfirmSignUpRequest() 213 | { 214 | Username = "User5", 215 | UserPoolId = poolCreated.Id 216 | }; 217 | AdminConfirmSignUpResponse confirmResponse = provider.AdminConfirmSignUpAsync(confirmRequest).Result; 218 | 219 | this.user = new CognitoUser("User5", clientCreated.ClientId, pool, provider); 220 | } 221 | 222 | /// 223 | /// Internal method that cleans up the created user pool (along with associated client/user) 224 | /// for testing 225 | /// 226 | public override void Dispose() 227 | { 228 | try 229 | { 230 | using (var client = new AmazonIdentityManagementServiceClient()) 231 | { 232 | client.DetachRolePolicyAsync(new DetachRolePolicyRequest() 233 | { 234 | PolicyArn = policyArn, 235 | RoleName = roleName 236 | }).Wait(); 237 | 238 | client.DeletePolicyAsync(new DeletePolicyRequest() 239 | { 240 | PolicyArn = policyArn 241 | }).Wait(); 242 | 243 | client.DeleteRoleAsync(new DeleteRoleRequest() 244 | { 245 | RoleName = roleName 246 | }).Wait(); 247 | } 248 | } 249 | catch (Exception ex) 250 | { 251 | System.Diagnostics.Debug.WriteLine(ex.Message); 252 | } 253 | 254 | base.Dispose(); 255 | } 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /test/Amazon.Extensions.CognitoAuthentication.IntegrationTests/SessionTests.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | using System; 17 | using System.Threading.Tasks; 18 | using Xunit; 19 | 20 | using Amazon.CognitoIdentityProvider.Model; 21 | using Amazon.Extensions.CognitoAuthentication.Util; 22 | using System.Linq; 23 | 24 | namespace Amazon.Extensions.CognitoAuthentication.IntegrationTests 25 | { 26 | public class SessionTests : AuthenticationConfirmUserTests 27 | { 28 | // Tests the ChangePassword method in CognitoUser to fail due to no valid session 29 | [Fact] 30 | public async Task TestFailedChangePassword() 31 | { 32 | await Assert.ThrowsAsync(() => user.ChangePasswordAsync("PassWord1!", "PassWord2!")); 33 | } 34 | 35 | // Tests that a CognitoUser object has a valid session object after being authenticated 36 | [Fact] 37 | public async Task TestValidSession() 38 | { 39 | AuthFlowResponse context = 40 | await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest() 41 | { 42 | Password = "PassWord1!" 43 | }); 44 | 45 | Assert.True(user.SessionTokens.IsValid()); 46 | } 47 | 48 | // Tests for successful use of the GetDetails method (requires valid session) 49 | [Fact] 50 | public async Task TestGetUserDetails() 51 | { 52 | AuthFlowResponse context = 53 | await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest() 54 | { 55 | Password = "PassWord1!" 56 | }); 57 | GetUserResponse userDetails = await user.GetUserDetailsAsync(); 58 | 59 | Assert.Contains(userDetails.UserAttributes, x => string.Equals(x.Name, CognitoConstants.UserAttrEmail, StringComparison.Ordinal)); 60 | Assert.Null(userDetails.MFAOptions); 61 | } 62 | 63 | //Tests the GlobalSignOut method 64 | [Fact] 65 | public async Task TestGlobalSignOut() 66 | { 67 | AuthFlowResponse context = 68 | await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest() 69 | { 70 | Password = "PassWord1!" 71 | }); 72 | 73 | await user.GlobalSignOutAsync(); 74 | 75 | Assert.Null(user.SessionTokens); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /test/Amazon.Extensions.CognitoAuthentication.IntegrationTests/_coreclr/BaseAuthenticationTestClass.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | using Amazon.CognitoIdentity; 17 | using Amazon.CognitoIdentityProvider; 18 | using Amazon.IdentityManagement; 19 | using Amazon.Runtime; 20 | using System; 21 | 22 | namespace Amazon.Extensions.CognitoAuthentication.IntegrationTests 23 | { 24 | /// 25 | /// Base class to be used for authentication integrations tests 26 | /// Allows for child classes to create, sign up, or confirm users 27 | /// 28 | public partial class BaseAuthenticationTestClass : IDisposable 29 | { 30 | protected RegionEndpoint clientRegion = FallbackRegionFactory.GetRegionEndpoint(); 31 | 32 | public IAmazonCognitoIdentityProvider GetAmazonCognitoIdentityProviderClient() 33 | { 34 | return new AmazonCognitoIdentityProviderClient(); 35 | } 36 | 37 | public AmazonCognitoIdentityClient GetAmazonCognitoIdentityClient() 38 | { 39 | return new AmazonCognitoIdentityClient(); 40 | } 41 | 42 | public AmazonIdentityManagementServiceClient GetAmazonIdentityManagementServiceClient() 43 | { 44 | return new AmazonIdentityManagementServiceClient(); 45 | } 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/Amazon.Extensions.CognitoAuthentication.UnitTests/Amazon.Extensions.CognitoAuthentication.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /test/Amazon.Extensions.CognitoAuthentication.UnitTests/AuthenticationCryptoTests.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | using System; 17 | using System.Globalization; 18 | using System.Numerics; 19 | using System.Security.Cryptography; 20 | using System.Text; 21 | using Amazon.Extensions.CognitoAuthentication; 22 | using Amazon.Extensions.CognitoAuthentication.Util; 23 | using Xunit; 24 | 25 | namespace CognitoAuthentication.UnitTests 26 | { 27 | public class AuthenticationCryptoTests 28 | { 29 | // Tests the entire SRP protocol using the tactics from the class implementation 30 | [Fact] 31 | public void TestSrpProtocol() 32 | { 33 | // Choose hash algorithm, g, N, username, password, salt 34 | SHA256 sha256 = SHA256.Create(); 35 | BigInteger g = AuthenticationHelper.g; 36 | BigInteger N = AuthenticationHelper.N; 37 | string password = "password"; 38 | string saltString = "fef2871d83ce2120f9c47a46db303d37"; //Does not have to always be this string 39 | BigInteger salt = BigIntegerExtensions.FromUnsignedLittleEndianHex(saltString); 40 | 41 | // Compute x = H(s,p) 42 | byte[] passBytes = Encoding.UTF8.GetBytes(password); 43 | byte[] userIdHash = sha256.ComputeHash(passBytes); 44 | byte[] saltBytes = salt.ToByteArray(); 45 | byte[] xBytes = new byte[saltBytes.Length + userIdHash.Length]; 46 | Buffer.BlockCopy(saltBytes, 0, xBytes, 0, saltBytes.Length); 47 | Buffer.BlockCopy(userIdHash, 0, xBytes, saltBytes.Length, userIdHash.Length); 48 | byte[] xDigest = sha256.ComputeHash(xBytes); 49 | BigInteger x = BigIntegerExtensions.FromUnsignedBigEndian(xDigest); 50 | 51 | // Compute v = g^x 52 | BigInteger v = BigInteger.ModPow(g, x, N); 53 | 54 | // Generate random a, b, A 55 | BigInteger a, b, A; 56 | do 57 | { 58 | a = AuthenticationHelper.CreateBigIntegerRandom(); 59 | b = AuthenticationHelper.CreateBigIntegerRandom(); 60 | A = BigInteger.ModPow(g, a, N); 61 | } while ((A.TrueMod(N)).Equals(BigInteger.Zero)); 62 | 63 | // Calculate k = H(N, g) 64 | byte[] nBytes = N.ToByteArray(); 65 | byte[] gBytes = g.ToByteArray(); 66 | byte[] content = new byte[nBytes.Length + gBytes.Length]; 67 | Buffer.BlockCopy(nBytes, 0, content, 0, nBytes.Length); 68 | Buffer.BlockCopy(gBytes, 0, content, nBytes.Length, gBytes.Length); 69 | byte[] digest = sha256.ComputeHash(content); 70 | BigInteger k = BigIntegerExtensions.FromUnsignedBigEndian(digest); 71 | 72 | //Calculate B = kv + g^b 73 | BigInteger B = k * v + (BigInteger.ModPow(g, b, N)); 74 | 75 | // Calculate u = H(A,B) 76 | byte[] ABytes = A.ToByteArray(); 77 | byte[] BBytes = B.ToByteArray(); 78 | byte[] ABcat = new byte[ABytes.Length + BBytes.Length]; 79 | Buffer.BlockCopy(ABytes, 0, ABcat, 0, ABytes.Length); 80 | Buffer.BlockCopy(BBytes, 0, ABcat, ABytes.Length, BBytes.Length); 81 | byte[] ABdigest = sha256.ComputeHash(ABcat); 82 | BigInteger u = BigIntegerExtensions.FromUnsignedBigEndian(ABdigest); 83 | 84 | // Calculate user side userS = (B - kg^x) ^ (a + ux) 85 | BigInteger userS = BigInteger.ModPow((B - k * BigInteger.ModPow(g, x, N)), a + u * x, N); 86 | 87 | // Calculate user side userK = H(userS) 88 | byte[] userSBytes = userS.ToByteArray(); 89 | byte[] userSDigest = sha256.ComputeHash(userSBytes); 90 | BigInteger userK = BigIntegerExtensions.FromUnsignedBigEndian(userSDigest); 91 | 92 | // Calculate host side hostS = (Av^u) ^ b 93 | BigInteger hostS = BigInteger.ModPow((A * BigInteger.ModPow(v, u, N)), b, N); 94 | 95 | // Calculate host side hostK = H(hostS) 96 | byte[] hostSBytes = hostS.ToByteArray(); 97 | byte[] hostSDigest = sha256.ComputeHash(hostSBytes); 98 | BigInteger hostK = BigIntegerExtensions.FromUnsignedBigEndian(hostSDigest); 99 | 100 | Assert.Equal(hostS, userS); 101 | Assert.Equal(hostK, userK); 102 | } 103 | 104 | // Checks that CreateTupleAa satisfies the SRP constraints 105 | [Fact] 106 | public void TestCreateTupleAa() 107 | { 108 | Tuple tuple = AuthenticationHelper.CreateAaTuple(); 109 | BigInteger A = tuple.Item1; 110 | BigInteger a = tuple.Item2; 111 | BigInteger g = AuthenticationHelper.g; 112 | 113 | Assert.Equal(A, BigInteger.ModPow(g, a, AuthenticationHelper.N)); 114 | Assert.NotEqual(A.TrueMod(AuthenticationHelper.N), BigInteger.Zero); 115 | } 116 | 117 | // Using information from a known working claim, checks that authenticateUser correctly reproduces that claim 118 | [Fact] 119 | public void TestAuthenticateUser() 120 | { 121 | string username = "User5"; 122 | string password = "Password1!"; 123 | string poolName = "Pj8nlkpKR"; 124 | string salt = "b704a27deb8cf5efec43a40eac5b60d2"; 125 | string srpb = "f6c76f03232c3a93a634f5e49a74efbfffcd2b81f0dacb2bce0274ae68601b3d42a98feb96ba6a310cc2d4183edac62" 126 | + "d571d0d220258d8753a80c4f8815a10953a21964f19c824916f46417f46d921d7956bf5c10c63e7b1c84ce6e2bc407b4e0a103a52" 127 | + "9313f439182c6450ed434d049e0d9dd5da8cf3a1d1f6f2839f55754a7b17444b8c55ff6b13bf63f61e473ad6fad9f4a1a0b2541b2" 128 | + "00de07c10296d679b36bf683245cee039842ad9579031e26cb0931ac202fe713fa2c64f920a9830722f708d5ede7ab0521363e45f" 129 | + "b2ad0afefbd65264c39f46d0c4011e2e8dbf8d6c53764719599a359b17bcd4d7e37c76e9e68adde0dc586a29f835a2decab335a49" 130 | + "408fbc8452d2696743f4465d12748851889d405d7287296e93f9abfb9f11806235bb588e8381a0060a174f5b0959eedba81b94eb3" 131 | + "83e80465323b1fc85478edcca4b67403a45b73ccbdcd9eaeacebd5f2ca32c7344fdbbc15a2a3d060c2b822851a282370a8dacbbfe" 132 | + "dbc511376a6e8ad95a5ce28fc2b9c37751c8d4314b9"; 133 | string secretBlock = "iZY+s7lYj4/5me1dgBZ9QR0vYOoatfBhec3Rc3kqPFAGIWAwxmaoXkOR1+kEvm0Aic/CxgXe0KHTTZuyKpsMfjbo2" 134 | + "J2YweeAtj47z6HCgVScBHeJ5il4NUhGJ6ehqWPls5ub233161UU/SxKAZYySbB8NNHIQidPNxEvBycU5gOeO9cOv4dlm3dwSRNFlg8OWJ" 135 | + "ysI7+ZXUgSWdfUBqog90wkIfLr7HirXnd3axiqpHqAApu4Ls7JFvgXxJa74ZJLtFobED/qSc4ALtRiAPwhKkomxhSXDF1S3dD+LcTQaR0" 136 | + "/gJY1+p1CuyTCQNBTVz5idOq8NhG/mMupKow7i54xWJp3gWCeRLMRYD5Od/b7QSgtBYZeLT2rDPb6T8oy8qScK4gq+1Ye6KmXTtmdc7XB" 137 | + "hwtyCk4xA8YBjeXO/Gq3G+rzpqMOS9e8P/EZ1xldkcme0qYesbUiKSVO30N2lPKo3qzWyq6zFi0pINpCiWLD+qdWa9Arofsml2IYgCxZw" 138 | + "moIRVk8vd/+UdhErVRFdOmMZWQVLki0K09UgrnvLLBcMxc4wq8myUmMivVpxg8vSaexrxGxDVnKZHgfl0jFCEWIYyMvPAspOo9bBT1l+o" 139 | + "QlVpf6RPKWlxgE4JSiWd9LD3PMdtETsyrWv1IxtktyzGI27UVanj0gGfesM56/FNImaxh+eoTBq4fJWxvfN0FvB90Ib/ZV+oJZ3jj8pKr" 140 | + "ZmDohutOQZqEIP5mme3UMGbkeF1zVnBuPT/Ujyb94RayMB4qvqnZap87o8gJ4iv0yp+HMAAxvPSvYJSr/dny/jEdsWM8aKU+a9+n3aMPo" 141 | + "/97DSIyiw2E3jcorR2emlichUHqdwDg/GAACUMTmV6peecDdhuF/KNZXBF4GEsoutDzPSsaXnQ11qKejULZt//fQ/e5JzWbUVhWXBOiRr" 142 | + "4JmbPdmWo9WIORrg/yrAlSe9RNnJOW+OCsAKLZYEHfuueUQYaxOpBTst+YQXzbwNYyX47H8nUDVXRLiA0zAoQHeTMXN8lL688MTICEUNa" 143 | + "JW9QWjrNIaTPic0oJ6CxVM/ayd8qv28EQ9BhyyE5yEreC9y+bpUQjTwxe62H/j0NiMsE5806dhx2RzxBlEYl51hs1aqwpQ6NGn51bwZPZ" 144 | + "5O7YitLyYdAoEdTo0hCXTKF2ITbSy8ZDv5cxQSv5zyXeHRfa/o1GSHWPawy3lYjHChVVj/ALBjpFVPJJwByQZqgXsrvuknWk7QFAi21ac" 145 | + "Qxm/4pqPVtFNVW7yZ51FawlQu6rKKfu1kyTJ6CKlcdXuJFv/NxQLPLq/to4b0UHNaD/mymwr0GSEn9GxpVB4fjhL4L2BRxCpt4lfLtx1K" 146 | + "MiTivUMIhrVShnrgUy2mpd/jooMsSf17GSvCYReg9dsaqvZXqbEMv6P2BX+RNXicLSfeDO9NyZhtuWZxyLJ5IlagOpVNbtGb/HCEQy+zF" 147 | + "8ySzho6e8vHxNodJX+xwCxRyKl6wbEdSEulWgm5WaUmsfySrNqLKassfrTdKLHyWUvjbWucEFzS0ddu+Q7j5RGaZ4MO7Qf1hN57WUE7ir" 148 | + "wXBc9UoAWI3Sggb+oDDuVKN5PMF6cL+3/yfetGX0Wrh9tk34z5pizLmOgqOqkMW5yFDqw8/pRqQA4Qo+VrpHL1a4vANgmOHw98918pFiC" 149 | + "G5lacOQn4FmA2QGWfNxU72qy6MLPnTgN/olShAFNFgckIn2F"; 150 | string timeStr = "Thu Jun 15 07:00:00 UTC 2017"; 151 | 152 | byte[] bytes = new byte[1024]; 153 | for (int i = bytes.Length - 1; i > 0; i--) 154 | { bytes[i] = 2; } 155 | BigInteger a = new BigInteger(bytes); 156 | BigInteger A = BigInteger.ModPow(AuthenticationHelper.g, a, AuthenticationHelper.N); 157 | Tuple tupleAa = Tuple.Create(A, a); 158 | 159 | byte[] claim = AuthenticationHelper.AuthenticateUser(username, password, poolName, 160 | tupleAa, salt, srpb, secretBlock, timeStr); 161 | string claimBase64 = Convert.ToBase64String(claim); 162 | string correctClaim = "QwHbbUqF6DSSepJh2QqTWDCb1XjmqaxnnW5kDn5dz7E="; 163 | 164 | Assert.Equal(correctClaim, claimBase64); 165 | } 166 | 167 | // Using infromation from a known working claim, checks that getPasswordAuthenticationKey generates the correct key 168 | [Fact] 169 | public void TestGetPasswordAuthenticationKey() 170 | { 171 | string username = "User5"; 172 | string password = "Password1!"; 173 | string poolName = "Pj8nlkpKR"; 174 | byte[] bytes = new byte[1024]; 175 | for (int i = bytes.Length - 1; i > 0; i--) 176 | { bytes[i] = 2; } 177 | BigInteger a = new BigInteger(bytes); 178 | BigInteger A = BigInteger.ModPow(AuthenticationHelper.g, a, AuthenticationHelper.N); 179 | Tuple tupleAa = Tuple.Create(A, a); 180 | string srpb = "8d340308265ada665b1b2c730fb65ff0b6dc746b63c2d7e9f08b8aa9306d4848268bc0c17ee4a2999173ca62af59fd74b" 181 | + "a5d00f16c96bea082b163f2c3a0b745455d62cb9577425b4b5d4dadba163a8e7759a7c0256795f464682770588c84e82f2c63d47017" 182 | + "51476da8e8a7b1a131e78560fe7b56b6761fcef88dcc486f403369a0cac2f04c25ed6d5e08b5a2f488500a0d5af4972cfa1213bfca3" 183 | + "7bbae189c8d58465b13193204f21ea01d267c7688e6e6cb5d3f5a8005db680d272308ee823816032dd2c8fc3b95b6fd0a742feb127e" 184 | + "3d094cd002a5e6b209415ff82f6abf50d5da43910a0336e9a6fc33dd101bf13f22f13fcb3ea3809aae4917d7c426fb8c0a894030c75" 185 | + "d3e15f0e1078a9d89e5154391cde6111ac14fab9fa3b3a880da7dbd47fd5a055937581d26b5d225c076e82f980dcbd77b3950d270d8" 186 | + "b622dca9c9bcd8fd6435a59b9690b3c9e2bdabf58cae3420c19066abc420145b1b66f226a6493c96588c2d53b637798fcaa573379f2" 187 | + "251848065fe1fafb68ed5e79135e9"; 188 | BigInteger B = BigIntegerExtensions.FromUnsignedLittleEndianHex(srpb); 189 | BigInteger salt = BigIntegerExtensions.FromUnsignedLittleEndianHex("b704a27deb8cf5efec43a40eac5b60d2"); 190 | 191 | byte[] key = AuthenticationHelper.GetPasswordAuthenticationKey(username, password, poolName, tupleAa, B, salt); 192 | string testKey = Convert.ToBase64String(key); 193 | string correctKey = "LmbBsy/4chqMRYOhmtmCrA=="; 194 | 195 | Assert.Equal(correctKey, testKey); 196 | } 197 | 198 | // Checks the secret hashing algoithm from the CognitoSecretHash class 199 | [Fact] 200 | public void TestSecretHash() 201 | { 202 | string hash = CognitoAuthHelper.GetUserPoolSecretHash("Mess", "age", "secret"); 203 | 204 | Assert.Equal("qnR8UCqJggD55PohusaBNviGoOJ67HC6Btry4qXLVZc=", hash); 205 | } 206 | 207 | // Using infromation from a known working claim, checks GetUserIdHash method from CognitoDeviceHelper 208 | [Fact] 209 | public void TestGetDeviceKeyHash() 210 | { 211 | string deviceGroupKey = "Pj8nlkpKR"; 212 | string deviceKey = "User5"; 213 | string password = "Password1!"; 214 | 215 | byte[] key = CognitoDeviceHelper.GetDeviceKeyHash(deviceGroupKey, deviceKey, password); 216 | string testKey = Convert.ToBase64String(key); 217 | string correctKey = "rJra60Rbj/4QW4UdYVl7wde78eMoiaw7Wk+WoDqo5K8="; 218 | 219 | Assert.Equal(correctKey, testKey); 220 | } 221 | 222 | //HKDF Test Vector 1 223 | [Fact] 224 | public void TestHkdfVector1() 225 | { 226 | // Expected Values 227 | byte[] prkFromSepc = CognitoAuthHelper.StringToByteArray("077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3" + 228 | "122ec844ad7c2b3e5"); 229 | byte[] okmFromSpec = CognitoAuthHelper.StringToByteArray("3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4" + 230 | "c5db02d56ecc4c5bf34007208d5b887185865"); 231 | 232 | byte[] ikmBytes = CognitoAuthHelper.StringToByteArray("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"); 233 | byte[] saltBytes = CognitoAuthHelper.StringToByteArray("000102030405060708090a0b0c"); 234 | byte[] infoBytes = CognitoAuthHelper.StringToByteArray("f0f1f2f3f4f5f6f7f8f9"); 235 | int length = 42; 236 | 237 | HkdfSha256 hkdfSha256 = new HkdfSha256(saltBytes, ikmBytes); 238 | byte[] hkdfResult = hkdfSha256.Expand(infoBytes, length); 239 | 240 | Assert.Equal(prkFromSepc, hkdfSha256.Prk); 241 | Assert.Equal(okmFromSpec, hkdfResult); 242 | } 243 | 244 | //HKDF Test Vector 2 245 | [Fact] 246 | public void TestHkdfVector2() 247 | { 248 | // Expected Values 249 | byte[] prkFromSpec = CognitoAuthHelper.StringToByteArray("06a6b88c5853361a06104c9ceb35b45cef760014904671" + 250 | "014a193f40c15fc244"); 251 | byte[] okmFromSpec = CognitoAuthHelper.StringToByteArray("b11e398dc80327a1c8e7f78c596a49344f012eda2d4efa" + 252 | "d8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71c" + 253 | "c30c58179ec3e87c14c01d5c1f3434f1d87"); 254 | 255 | byte[] ikmBytes = CognitoAuthHelper.StringToByteArray("000102030405060708090a0b0c0d0e0f101112131" + 256 | "415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d" + 257 | "3e3f404142434445464748494a4b4c4d4e4f"); 258 | byte[] saltBytes = CognitoAuthHelper.StringToByteArray("606162636465666768696a6b6c6d6e6f70717273" + 259 | "7475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9" + 260 | "d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf"); 261 | byte[] infoBytes = CognitoAuthHelper.StringToByteArray("b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3" + 262 | "c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebece" + 263 | "deeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"); 264 | int length = 82; 265 | 266 | HkdfSha256 hkdfSha256 = new HkdfSha256(saltBytes, ikmBytes); 267 | byte[] hkdfResult = hkdfSha256.Expand(infoBytes, length); 268 | 269 | Assert.Equal(prkFromSpec, hkdfSha256.Prk); 270 | Assert.Equal(okmFromSpec, hkdfResult); 271 | } 272 | 273 | //HKDF Test Vector 3 274 | [Fact] 275 | public void TestHkdfVector3() 276 | { 277 | // Expected Values 278 | byte[] prkFromSpec = CognitoAuthHelper.StringToByteArray("19ef24a32c717b167f33a91d6f648bdf96596776afdb6" + 279 | "377ac434c1c293ccb04"); 280 | byte[] okmFromSpec = CognitoAuthHelper.StringToByteArray("8da4e775a563c18f715f802a063c5a31b8a11f5c5ee18" + 281 | "79ec3454e5f3c738d2d9d201395faa4b61a96c8"); 282 | 283 | byte[] ikmBytes = CognitoAuthHelper.StringToByteArray("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"); 284 | byte[] saltBytes = CognitoAuthHelper.StringToByteArray(""); 285 | byte[] infoBytes = CognitoAuthHelper.StringToByteArray(""); 286 | int length = 42; 287 | 288 | HkdfSha256 hkdfSha256 = new HkdfSha256(saltBytes, ikmBytes); 289 | byte[] hkdfResult = hkdfSha256.Expand(infoBytes, length); 290 | 291 | Assert.Equal(prkFromSpec, hkdfSha256.Prk); 292 | Assert.Equal(okmFromSpec, hkdfResult); 293 | } 294 | } 295 | } -------------------------------------------------------------------------------- /test/Amazon.Extensions.CognitoAuthentication.UnitTests/BigIntegerExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using Amazon.Extensions.CognitoAuthentication.Util; 2 | using System.Globalization; 3 | using System.Linq; 4 | using System.Numerics; 5 | using Xunit; 6 | 7 | namespace Amazon.Extensions.CognitoAuthentication.UnitTests 8 | { 9 | public class BigIntegerExtensionsTests 10 | { 11 | [Fact] 12 | public void TestFromHexPositiveTest() 13 | { 14 | TestFromHexPositive("0", 0, 0); 15 | TestFromHexPositive("1", 1, 1); 16 | TestFromHexPositive("2", 2, 2); 17 | TestFromHexPositive("3", 3, 3); 18 | TestFromHexPositive("4", 4, 4); 19 | TestFromHexPositive("5", 5, 5); 20 | TestFromHexPositive("6", 6, 6); 21 | TestFromHexPositive("7", 7, 7); 22 | TestFromHexPositive("8", 8, -8); 23 | TestFromHexPositive("9", 9, -7); 24 | TestFromHexPositive("A", 10, -6); 25 | TestFromHexPositive("B", 11, -5); 26 | TestFromHexPositive("C", 12, -4); 27 | TestFromHexPositive("D", 13, -3); 28 | TestFromHexPositive("E", 14, -2); 29 | TestFromHexPositive("F", 15, -1); 30 | 31 | TestFromHexPositive("00", 0, 0); 32 | TestFromHexPositive("10", 16, 16); 33 | TestFromHexPositive("20", 32, 32); 34 | TestFromHexPositive("30", 48, 48); 35 | TestFromHexPositive("40", 64, 64); 36 | TestFromHexPositive("50", 80, 80); 37 | TestFromHexPositive("60", 96, 96); 38 | TestFromHexPositive("70", 112, 112); 39 | TestFromHexPositive("80", 128, -128); 40 | TestFromHexPositive("90", 144, -112); 41 | TestFromHexPositive("A0", 160, -96); 42 | TestFromHexPositive("B0", 176, -80); 43 | TestFromHexPositive("C0", 192, -64); 44 | TestFromHexPositive("D0", 208, -48); 45 | TestFromHexPositive("E0", 224, -32); 46 | TestFromHexPositive("F0", 240, -16); 47 | } 48 | 49 | private void TestFromHexPositive(string hex, int expectedFromHexPositive, int expectedFromHexRegular) 50 | { 51 | Assert.Equal(new BigInteger(expectedFromHexPositive), BigIntegerExtensions.FromUnsignedLittleEndianHex(hex)); 52 | Assert.Equal(new BigInteger(expectedFromHexRegular), BigInteger.Parse(hex, NumberStyles.HexNumber)); 53 | } 54 | 55 | [Fact] 56 | public void TestTrueModTest() 57 | { 58 | TestTrueMod(10, 3, 1); 59 | TestTrueMod(10, 5, 0); 60 | TestTrueMod(-10, 3, 2); 61 | TestTrueMod(-10, 5, 0); 62 | } 63 | 64 | private void TestTrueMod(int numerator, int denominator, int expectedTrueMod) 65 | { 66 | var biNumerator = new BigInteger(numerator); 67 | var biDenominator = new BigInteger(denominator); 68 | 69 | var biTrueMod = biNumerator.TrueMod(biDenominator); 70 | 71 | Assert.Equal(expectedTrueMod, biTrueMod); 72 | } 73 | 74 | [Fact] 75 | public void TestFromBigEndianPositiveTest() 76 | { 77 | TestFromBigEndianPositive(300, new byte[] { 1, 44 }, 300); 78 | TestFromBigEndianPositive(-266, new byte[] { 254, 246 }, 65270); 79 | } 80 | 81 | private void TestFromBigEndianPositive(int number, byte[] expectedBigEndianByteArray, int expectedFromBigEndianPositvie) 82 | { 83 | var biNumber = new BigInteger(number); 84 | var bigEndianByteArray = biNumber.ToBigEndianByteArray(); 85 | var biFromBigEndianPositive = BigIntegerExtensions.FromUnsignedBigEndian(bigEndianByteArray); 86 | 87 | Assert.True(expectedBigEndianByteArray.SequenceEqual(bigEndianByteArray)); 88 | Assert.Equal(expectedFromBigEndianPositvie, biFromBigEndianPositive); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /test/Amazon.Extensions.CognitoAuthentication.UnitTests/SecurityComplianceTests.cs: -------------------------------------------------------------------------------- 1 | using Amazon.Extensions.CognitoAuthentication.Util; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Xunit; 6 | 7 | namespace Amazon.Extensions.CognitoAuthentication.UnitTests 8 | { 9 | public class SecurityComplianceTests 10 | { 11 | private const int ModulusBitSize = 3072; 12 | private const int EphemeralBitSize = 256; 13 | 14 | private const int BitsInByte = 8; 15 | 16 | [Fact] 17 | public void ModulusLengthTest() 18 | { 19 | // The -1 is to account for the byte that BigInteger.ToByteArray() adds to account for the sign. 20 | var modulusLength = (AuthenticationHelper.N.ToBigEndianByteArray().Length - 1) * BitsInByte; 21 | Assert.Equal(ModulusBitSize, modulusLength); 22 | } 23 | 24 | [Fact] 25 | public void EphemeralKeyLengthTest() 26 | { 27 | // The -1 is to account for the byte that BigInteger.ToByteArray() adds to account for the sign. 28 | var ephemeralLengthBits = (AuthenticationHelper.CreateBigIntegerRandom().ToBigEndianByteArray().Length - 1) * BitsInByte; 29 | Assert.True(ephemeralLengthBits >= EphemeralBitSize); 30 | } 31 | } 32 | } 33 | --------------------------------------------------------------------------------