├── .gitattributes ├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── _config.yml ├── samples ├── ApiKeySamplesClient.http ├── ApiKeySamplesClient.postman_collection.json ├── SampleWebApi.Shared │ ├── GlobalSuppressions.cs │ ├── Models │ │ └── ApiKey.cs │ ├── Repositories │ │ ├── IApiKeyRepository.cs │ │ └── InMemoryApiKeyRepository.cs │ ├── SampleWebApi.Shared.projitems │ ├── SampleWebApi.Shared.shproj │ └── Services │ │ └── ApiKeyProvider.cs ├── SampleWebApi_2_0 │ ├── Controllers │ │ └── ValuesController.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── SampleWebApi_2_0.csproj │ ├── Startup.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── SampleWebApi_2_2 │ ├── Controllers │ │ └── ValuesController.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── SampleWebApi_2_2.csproj │ ├── Startup.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── SampleWebApi_3_1 │ ├── Controllers │ │ └── ValuesController.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── SampleWebApi_3_1.csproj │ ├── Startup.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── SampleWebApi_5_0 │ ├── Controllers │ │ └── ValuesController.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── SampleWebApi_5_0.csproj │ ├── Startup.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── SampleWebApi_6_0 │ ├── Controllers │ │ └── ValuesController.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── SampleWebApi_6_0.csproj │ ├── appsettings.Development.json │ └── appsettings.json ├── SampleWebApi_7_0 │ ├── Controllers │ │ └── ValuesController.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── SampleWebApi_7_0.csproj │ ├── appsettings.Development.json │ └── appsettings.json ├── SampleWebApi_8_0 │ ├── Controllers │ │ └── ValuesController.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── SampleWebApi_8_0.csproj │ ├── appsettings.Development.json │ └── appsettings.json ├── SampleWebApi_9_0 │ ├── Controllers │ │ └── ValuesController.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── SampleWebApi_9_0.csproj │ ├── appsettings.Development.json │ └── appsettings.json └── SampleWebApi_AOT │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── SampleWebApi_AOT.csproj │ ├── appsettings.Development.json │ └── appsettings.json ├── src ├── AspNetCore.Authentication.ApiKey.sln ├── AspNetCore.Authentication.ApiKey │ ├── ApiKeyDefaults.cs │ ├── ApiKeyExtensions.cs │ ├── ApiKeyHandlerBase.cs │ ├── ApiKeyInAuthorizationHeaderHandler.cs │ ├── ApiKeyInHeaderHandler.cs │ ├── ApiKeyInHeaderOrQueryParamsHandler.cs │ ├── ApiKeyInQueryParamsHandler.cs │ ├── ApiKeyInRouteValuesHandler.cs │ ├── ApiKeyOptions.cs │ ├── ApiKeyPostConfigureOptions.cs │ ├── ApiKeyUtils.cs │ ├── AspNetCore.Authentication.ApiKey.csproj │ ├── CompatibilitySuppressions.xml │ ├── Events │ │ ├── ApiKeyAuthenticationFailedContext.cs │ │ ├── ApiKeyAuthenticationSucceededContext.cs │ │ ├── ApiKeyEvents.cs │ │ ├── ApiKeyHandleChallengeContext.cs │ │ ├── ApiKeyHandleForbiddenContext.cs │ │ └── ApiKeyValidateKeyContext.cs │ ├── GlobalSuppressions.cs │ ├── IApiKey.cs │ └── IApiKeyProvider.cs └── key.snk └── test └── AspNetCore.Authentication.ApiKey.Tests ├── ApiKeyDefaultsTests.cs ├── ApiKeyExtensionsTests.cs ├── ApiKeyHandlerBaseTests.cs ├── ApiKeyInAuthorizationHeaderHandlerTests.cs ├── ApiKeyInHeaderHandlerTests.cs ├── ApiKeyInHeaderOrQueryParamsHandlerTests.cs ├── ApiKeyInQueryParamsHandlerTests.cs ├── ApiKeyInRouteValuesHandlerTests.cs ├── ApiKeyOptionsTests.cs ├── ApiKeyPostConfigureOptionsTests.cs ├── ApiKeyUtilsTests.cs ├── AspNetCore.Authentication.ApiKey.Tests.csproj ├── Events ├── ApiKeyAuthenticationFailedContextTests.cs ├── ApiKeyAuthenticationSucceededContextTests.cs ├── ApiKeyHandleChallengeContextTests.cs ├── ApiKeyHandleForbiddenContextTests.cs └── ApiKeyValidateKeyContextTests.cs ├── GlobalSuppressions.cs └── Infrastructure ├── ClaimsPrincipalDto.cs ├── FakeApiKeyProvider.cs └── TestServerBuilder.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "CodeQL" 7 | 8 | on: 9 | push: 10 | branches: [master] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [master] 14 | schedule: 15 | - cron: '0 5 * * 0' 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | # Override automatic language detection by changing the below list 26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 27 | language: ['csharp'] 28 | # Learn more... 29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v2 34 | 35 | - name: Setup .NET Core SDK 36 | uses: actions/setup-dotnet@v1.9.0 37 | with: 38 | dotnet-version: 9.x.x 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v2 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | 51 | 52 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 53 | # If this step fails, then you should remove it and run the build manually (see below) 54 | - name: Autobuild 55 | uses: github/codeql-action/autobuild@v2 56 | 57 | # ℹ️ Command-line programs to run using the OS shell. 58 | # 📚 https://git.io/JvXDl 59 | 60 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 61 | # and modify them (or add more) to build your code if your project 62 | # uses a compiled language 63 | 64 | #- run: | 65 | # make bootstrap 66 | # make release 67 | 68 | - name: Perform CodeQL Analysis 69 | uses: github/codeql-action/analyze@v2 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Sorry, not accepting any contribution or pull requests at the moment. Thank you for understanding. 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Mihir Dilip 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | title: '' 2 | -------------------------------------------------------------------------------- /samples/ApiKeySamplesClient.http: -------------------------------------------------------------------------------- 1 | @HostAddress = https://localhost:44304 2 | @ApiKey = Key1 3 | 4 | # Get Values No Auth 5 | GET {{HostAddress}}/api/values 6 | Accept: application/json 7 | 8 | ### 9 | 10 | # Get Values 11 | GET {{HostAddress}}/api/values 12 | Accept: application/json 13 | X-API-Key: {{ApiKey}} 14 | 15 | ### 16 | 17 | # Claims 18 | GET {{HostAddress}}/api/values/claims 19 | Accept: application/json 20 | X-API-Key: {{ApiKey}} 21 | 22 | ### 23 | 24 | # Forbid 25 | GET {{HostAddress}}/api/values/forbid 26 | Accept: application/json 27 | X-API-Key: {{ApiKey}} -------------------------------------------------------------------------------- /samples/ApiKeySamplesClient.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "d1468654-1b2a-4caf-9380-6ae0e6d03f6f", 4 | "name": "ApiKeySamplesClient", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "Get Values No Auth", 10 | "request": { 11 | "auth": { 12 | "type": "noauth" 13 | }, 14 | "method": "GET", 15 | "header": [], 16 | "url": { 17 | "raw": "{{base_url}}/api/values", 18 | "host": [ 19 | "{{base_url}}" 20 | ], 21 | "path": [ 22 | "api", 23 | "values" 24 | ] 25 | } 26 | }, 27 | "response": [] 28 | }, 29 | { 30 | "name": "Get Values", 31 | "request": { 32 | "method": "GET", 33 | "header": [], 34 | "url": { 35 | "raw": "{{base_url}}/api/values", 36 | "host": [ 37 | "{{base_url}}" 38 | ], 39 | "path": [ 40 | "api", 41 | "values" 42 | ] 43 | } 44 | }, 45 | "response": [] 46 | }, 47 | { 48 | "name": "Claims", 49 | "request": { 50 | "method": "GET", 51 | "header": [], 52 | "url": { 53 | "raw": "{{base_url}}/api/values/claims", 54 | "host": [ 55 | "{{base_url}}" 56 | ], 57 | "path": [ 58 | "api", 59 | "values", 60 | "claims" 61 | ] 62 | } 63 | }, 64 | "response": [] 65 | }, 66 | { 67 | "name": "Forbid", 68 | "request": { 69 | "method": "GET", 70 | "header": [], 71 | "url": { 72 | "raw": "{{base_url}}/api/values/forbid", 73 | "host": [ 74 | "{{base_url}}" 75 | ], 76 | "path": [ 77 | "api", 78 | "values", 79 | "forbid" 80 | ] 81 | } 82 | }, 83 | "response": [] 84 | } 85 | ], 86 | "auth": { 87 | "type": "apikey", 88 | "apikey": [ 89 | { 90 | "key": "value", 91 | "value": "Key1", 92 | "type": "string" 93 | }, 94 | { 95 | "key": "key", 96 | "value": "X-API-Key", 97 | "type": "string" 98 | } 99 | ] 100 | }, 101 | "event": [ 102 | { 103 | "listen": "prerequest", 104 | "script": { 105 | "id": "28b765c6-41cd-4b65-8801-02fba6cac26b", 106 | "type": "text/javascript", 107 | "exec": [ 108 | "" 109 | ] 110 | } 111 | }, 112 | { 113 | "listen": "test", 114 | "script": { 115 | "id": "939052e9-9216-4135-b1c9-45ce33bcfc6f", 116 | "type": "text/javascript", 117 | "exec": [ 118 | "" 119 | ] 120 | } 121 | } 122 | ], 123 | "variable": [ 124 | { 125 | "id": "c46c2023-75a9-4c27-8c41-dbb222f466af", 126 | "key": "base_url", 127 | "value": "https://localhost:44304/" 128 | } 129 | ], 130 | "protocolProfileBehavior": {} 131 | } -------------------------------------------------------------------------------- /samples/SampleWebApi.Shared/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Style", "IDE0028:Simplify collection initialization", Justification = "", Scope = "member", Target = "~F:SampleWebApi.Repositories.InMemoryApiKeyRepository._cache")] 9 | [assembly: SuppressMessage("Style", "IDE0090:Use 'new(...)'", Justification = "", Scope = "member", Target = "~F:SampleWebApi.Repositories.InMemoryApiKeyRepository._cache")] 10 | [assembly: SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "", Scope = "member", Target = "~M:SampleWebApi.Models.ApiKey.#ctor(System.String,System.String,System.Collections.Generic.List{System.Security.Claims.Claim})")] 11 | [assembly: SuppressMessage("Style", "IDE0028:Simplify collection initialization", Justification = "", Scope = "member", Target = "~M:SampleWebApi.Models.ApiKey.#ctor(System.String,System.String,System.Collections.Generic.List{System.Security.Claims.Claim})")] 12 | [assembly: SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "", Scope = "member", Target = "~M:SampleWebApi.Services.ApiKeyProvider.#ctor(Microsoft.Extensions.Logging.ILogger{AspNetCore.Authentication.ApiKey.IApiKeyProvider},SampleWebApi.Repositories.IApiKeyRepository)")] 13 | 14 | [assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "", Scope = "member", Target = "~M:SampleWebApi.Services.ApiKeyProvider.ProvideAsync(System.String)~System.Threading.Tasks.Task{AspNetCore.Authentication.ApiKey.IApiKey}")] 15 | 16 | -------------------------------------------------------------------------------- /samples/SampleWebApi.Shared/Models/ApiKey.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. 2 | using AspNetCore.Authentication.ApiKey; 3 | using System.Collections.Generic; 4 | using System.Security.Claims; 5 | 6 | namespace SampleWebApi.Models 7 | { 8 | class ApiKey : IApiKey 9 | { 10 | public ApiKey(string key, string owner, List claims = null) 11 | { 12 | Key = key; 13 | OwnerName = owner; 14 | Claims = claims ?? new List(); 15 | } 16 | 17 | public string Key { get; } 18 | public string OwnerName { get; } 19 | public IReadOnlyCollection Claims { get; } 20 | } 21 | } 22 | #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. 23 | -------------------------------------------------------------------------------- /samples/SampleWebApi.Shared/Repositories/IApiKeyRepository.cs: -------------------------------------------------------------------------------- 1 | using AspNetCore.Authentication.ApiKey; 2 | using System.Threading.Tasks; 3 | 4 | namespace SampleWebApi.Repositories 5 | { 6 | /// 7 | /// NOTE: DO NOT USE THIS IMPLEMENTATION. THIS IS FOR DEMO PURPOSE ONLY 8 | /// 9 | public interface IApiKeyRepository 10 | { 11 | Task GetApiKeyAsync(string key); 12 | } 13 | } -------------------------------------------------------------------------------- /samples/SampleWebApi.Shared/Repositories/InMemoryApiKeyRepository.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CS8619 // Nullability of reference types in value doesn't match target type. 2 | using AspNetCore.Authentication.ApiKey; 3 | using SampleWebApi.Models; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace SampleWebApi.Repositories 10 | { 11 | /// 12 | /// NOTE: DO NOT USE THIS IMPLEMENTATION. THIS IS FOR DEMO PURPOSE ONLY 13 | /// 14 | public class InMemoryApiKeyRepository : IApiKeyRepository 15 | { 16 | private readonly List _cache = new List() 17 | { 18 | new ApiKey("Key1", "Admin"), 19 | new ApiKey("Key2", "User"), 20 | }; 21 | 22 | public Task GetApiKeyAsync(string key) 23 | { 24 | var apiKey = _cache.FirstOrDefault(k => k.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); 25 | return Task.FromResult(apiKey); 26 | } 27 | } 28 | } 29 | #pragma warning restore CS8619 // Nullability of reference types in value doesn't match target type. 30 | -------------------------------------------------------------------------------- /samples/SampleWebApi.Shared/SampleWebApi.Shared.projitems: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | true 6 | e544fb20-29f3-41f5-a78e-6164f9c43b3c 7 | 8 | 9 | SampleWebApi.Shared 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /samples/SampleWebApi.Shared/SampleWebApi.Shared.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | e544fb20-29f3-41f5-a78e-6164f9c43b3c 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /samples/SampleWebApi.Shared/Services/ApiKeyProvider.cs: -------------------------------------------------------------------------------- 1 | using AspNetCore.Authentication.ApiKey; 2 | using Microsoft.Extensions.Logging; 3 | using SampleWebApi.Repositories; 4 | using System.Threading.Tasks; 5 | 6 | namespace SampleWebApi.Services 7 | { 8 | class ApiKeyProvider : IApiKeyProvider 9 | { 10 | private readonly ILogger _logger; 11 | private readonly IApiKeyRepository _apiKeyRepository; 12 | 13 | public ApiKeyProvider(ILogger logger, IApiKeyRepository apiKeyRepository) 14 | { 15 | _logger = logger; 16 | _apiKeyRepository = apiKeyRepository; 17 | } 18 | 19 | public async Task ProvideAsync(string key) 20 | { 21 | try 22 | { 23 | return await _apiKeyRepository.GetApiKeyAsync(key); 24 | } 25 | catch (System.Exception exception) 26 | { 27 | _logger.LogError(exception, exception.Message); 28 | throw; 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /samples/SampleWebApi_2_0/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SampleWebApi_2_2.Controllers 6 | { 7 | [Route("api/[controller]")] 8 | public class ValuesController : ControllerBase 9 | { 10 | // GET api/values 11 | [HttpGet] 12 | public IEnumerable Get() 13 | { 14 | return new string[] { "value1", "value2" }; 15 | } 16 | 17 | [HttpGet("claims")] 18 | public string Claims() 19 | { 20 | var sb = new StringBuilder(); 21 | foreach (var claim in User.Claims) 22 | { 23 | sb.AppendLine($"{claim.Type}: {claim.Value}"); 24 | } 25 | return sb.ToString(); 26 | } 27 | 28 | [HttpGet("forbid")] 29 | public new IActionResult Forbid() 30 | { 31 | return base.Forbid(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /samples/SampleWebApi_2_0/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace SampleWebApi 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | BuildWebHost(args).Run(); 11 | } 12 | 13 | public static IWebHost BuildWebHost(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup() 16 | .Build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/SampleWebApi_2_0/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:3920/", 7 | "sslPort": 44304 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "launchUrl": "api/values", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "SampleWebApi": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "launchUrl": "api/values", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /samples/SampleWebApi_2_0/SampleWebApi_2_0.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | false 6 | false 7 | latest 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /samples/SampleWebApi_2_0/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/SampleWebApi_2_0/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "Debug": { 5 | "LogLevel": { 6 | "Default": "Warning" 7 | } 8 | }, 9 | "Console": { 10 | "LogLevel": { 11 | "Default": "Warning" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/SampleWebApi_2_2/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SampleWebApi_2_2.Controllers 6 | { 7 | [Route("api/[controller]")] 8 | [ApiController] 9 | public class ValuesController : ControllerBase 10 | { 11 | // GET api/values 12 | [HttpGet] 13 | public ActionResult> Get() 14 | { 15 | return new string[] { "value1", "value2" }; 16 | } 17 | 18 | [HttpGet("claims")] 19 | public ActionResult Claims() 20 | { 21 | var sb = new StringBuilder(); 22 | foreach (var claim in User.Claims) 23 | { 24 | sb.AppendLine($"{claim.Type}: {claim.Value}"); 25 | } 26 | return sb.ToString(); 27 | } 28 | 29 | [HttpGet("forbid")] 30 | public new IActionResult Forbid() 31 | { 32 | return base.Forbid(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /samples/SampleWebApi_2_2/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace SampleWebApi_2_2 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateWebHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/SampleWebApi_2_2/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:3920", 8 | "sslPort": 44304 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "api/values", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "SampleWebApi_2_2": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "api/values", 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /samples/SampleWebApi_2_2/SampleWebApi_2_2.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.2 5 | InProcess 6 | false 7 | false 8 | latest 9 | enable 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /samples/SampleWebApi_2_2/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/SampleWebApi_2_2/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /samples/SampleWebApi_3_1/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SampleWebApi_3_1.Controllers 6 | { 7 | [Route("api/[controller]")] 8 | [ApiController] 9 | public class ValuesController : ControllerBase 10 | { 11 | // GET api/values 12 | [HttpGet] 13 | public ActionResult> Get() 14 | { 15 | return new string[] { "value1", "value2" }; 16 | } 17 | 18 | [HttpGet("claims")] 19 | public ActionResult Claims() 20 | { 21 | var sb = new StringBuilder(); 22 | foreach (var claim in User.Claims) 23 | { 24 | sb.AppendLine($"{claim.Type}: {claim.Value}"); 25 | } 26 | return sb.ToString(); 27 | } 28 | 29 | [HttpGet("forbid")] 30 | public new IActionResult Forbid() 31 | { 32 | return base.Forbid(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /samples/SampleWebApi_3_1/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace SampleWebApi_3_1 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /samples/SampleWebApi_3_1/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:3920", 7 | "sslPort": 44304 8 | } 9 | }, 10 | "$schema": "http://json.schemastore.org/launchsettings.json", 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "api/values", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "SampleWebApi_3_1": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "api/values", 24 | "environmentVariables": { 25 | "ASPNETCORE_ENVIRONMENT": "Development" 26 | }, 27 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /samples/SampleWebApi_3_1/SampleWebApi_3_1.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | enable 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /samples/SampleWebApi_3_1/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/SampleWebApi_3_1/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /samples/SampleWebApi_5_0/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SampleWebApi_5_0.Controllers 6 | { 7 | [Route("api/[controller]")] 8 | [ApiController] 9 | public class ValuesController : ControllerBase 10 | { 11 | // GET api/values 12 | [HttpGet] 13 | public ActionResult> Get() 14 | { 15 | return new string[] { "value1", "value2" }; 16 | } 17 | 18 | [HttpGet("claims")] 19 | public ActionResult Claims() 20 | { 21 | var sb = new StringBuilder(); 22 | foreach (var claim in User.Claims) 23 | { 24 | sb.AppendLine($"{claim.Type}: {claim.Value}"); 25 | } 26 | return sb.ToString(); 27 | } 28 | 29 | [HttpGet("forbid")] 30 | public new IActionResult Forbid() 31 | { 32 | return base.Forbid(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /samples/SampleWebApi_5_0/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace SampleWebApi_5_0 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /samples/SampleWebApi_5_0/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:3920", 8 | "sslPort": 44304 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "api/values", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "SampleWebApi_5_0": { 21 | "commandName": "Project", 22 | "dotnetRunMessages": "true", 23 | "launchBrowser": true, 24 | "launchUrl": "api/values", 25 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/SampleWebApi_5_0/SampleWebApi_5_0.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | enable 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /samples/SampleWebApi_5_0/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/SampleWebApi_5_0/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /samples/SampleWebApi_6_0/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SampleWebApi_6_0.Controllers 6 | { 7 | [Route("api/[controller]")] 8 | [ApiController] 9 | public class ValuesController : ControllerBase 10 | { 11 | // GET api/values 12 | [HttpGet] 13 | public ActionResult> Get() 14 | { 15 | return new string[] { "value1", "value2" }; 16 | } 17 | 18 | [HttpGet("claims")] 19 | public ActionResult Claims() 20 | { 21 | var sb = new StringBuilder(); 22 | foreach (var claim in User.Claims) 23 | { 24 | sb.AppendLine($"{claim.Type}: {claim.Value}"); 25 | } 26 | return sb.ToString(); 27 | } 28 | 29 | [HttpGet("forbid")] 30 | public new IActionResult Forbid() 31 | { 32 | return base.Forbid(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /samples/SampleWebApi_6_0/Program.cs: -------------------------------------------------------------------------------- 1 | using AspNetCore.Authentication.ApiKey; 2 | using Microsoft.AspNetCore.Authorization; 3 | using SampleWebApi.Repositories; 4 | using SampleWebApi.Services; 5 | 6 | var builder = WebApplication.CreateBuilder(args); 7 | 8 | // Add User repository to the dependency container. 9 | builder.Services.AddTransient(); 10 | 11 | // Add the ApiKey scheme authentication here.. 12 | // It requires Realm to be set in the options if SuppressWWWAuthenticateHeader is not set. 13 | // If an implementation of IApiKeyProvider interface is registered in the dependency register as well as OnValidateKey delegete on options.Events is also set then this delegate will be used instead of an implementation of IApiKeyProvider. 14 | builder.Services.AddAuthentication(ApiKeyDefaults.AuthenticationScheme) 15 | 16 | // The below AddApiKeyInHeaderOrQueryParams without type parameter will require OnValidateKey delegete on options.Events to be set unless an implementation of IApiKeyProvider interface is registered in the dependency register. 17 | // Please note if OnValidateKey delegete on options.Events is also set then this delegate will be used instead of ApiKeyProvider.* 18 | //.AddApiKeyInHeaderOrQueryParams(options => 19 | 20 | // The below AddApiKeyInHeaderOrQueryParams with type parameter will add the ApiKeyProvider to the dependency register. 21 | // Please note if OnValidateKey delegete on options.Events is also set then this delegate will be used instead of ApiKeyProvider. 22 | .AddApiKeyInHeaderOrQueryParams(options => 23 | { 24 | options.Realm = "Sample Web API"; 25 | options.KeyName = "X-API-KEY"; 26 | 27 | //// Optional option to suppress the browser login dialog for ajax calls. 28 | //options.SuppressWWWAuthenticateHeader = true; 29 | 30 | //// Optional option to ignore extra check of ApiKey string after it is validated. 31 | //options.ForLegacyIgnoreExtraValidatedApiKeyCheck = true; 32 | 33 | //// Optional option to ignore authentication if AllowAnonumous metadata/filter attribute is added to an endpoint. 34 | //options.IgnoreAuthenticationIfAllowAnonymous = true; 35 | 36 | //// Optional events to override the ApiKey original logic with custom logic. 37 | //// Only use this if you know what you are doing at your own risk. Any of the events can be assigned. 38 | options.Events = new ApiKeyEvents 39 | { 40 | 41 | //// A delegate assigned to this property will be invoked just before validating the api key. 42 | //OnValidateKey = async (context) => 43 | //{ 44 | // // custom code to handle the api key, create principal and call Success method on context. 45 | // var apiKeyRepository = context.HttpContext.RequestServices.GetRequiredService(); 46 | // var apiKey = await apiKeyRepository.GetApiKeyAsync(context.ApiKey); 47 | // var isValid = apiKey != null && apiKey.Key.Equals(context.ApiKey, StringComparison.OrdinalIgnoreCase); 48 | // if (isValid) 49 | // { 50 | // context.Response.Headers.Add("ValidationCustomHeader", "From OnValidateKey"); 51 | // var claims = new[] 52 | // { 53 | // new Claim(ClaimTypes.NameIdentifier, apiKey.OwnerName, ClaimValueTypes.String, context.Options.ClaimsIssuer), 54 | // new Claim(ClaimTypes.Name, apiKey.OwnerName, ClaimValueTypes.String, context.Options.ClaimsIssuer), 55 | // new Claim("CustomClaimType", "Custom Claim Value - from OnValidateKey") 56 | // }; 57 | // context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name)); 58 | // context.Success(); 59 | // } 60 | // else 61 | // { 62 | // context.NoResult(); 63 | // } 64 | //}, 65 | 66 | //// A delegate assigned to this property will be invoked just before validating the api key. 67 | //// NOTE: Same as above delegate but slightly different implementation which will give same result. 68 | //OnValidateKey = async (context) => 69 | //{ 70 | // // custom code to handle the api key, create principal and call Success method on context. 71 | // var apiKeyRepository = context.HttpContext.RequestServices.GetRequiredService(); 72 | // var apiKey = await apiKeyRepository.GetApiKeyAsync(context.ApiKey); 73 | // var isValid = apiKey != null && apiKey.Key.Equals(context.ApiKey, StringComparison.OrdinalIgnoreCase); 74 | // if (isValid) 75 | // { 76 | // context.Response.Headers.Add("ValidationCustomHeader", "From OnValidateKey"); 77 | // var claims = new[] 78 | // { 79 | // new Claim("CustomClaimType", "Custom Claim Value - from OnValidateKey") 80 | // }; 81 | // context.ValidationSucceeded(apiKey.OwnerName, claims); // claims are optional 82 | // } 83 | // else 84 | // { 85 | // context.ValidationFailed(); 86 | // } 87 | //}, 88 | 89 | //// A delegate assigned to this property will be invoked before a challenge is sent back to the caller when handling unauthorized response. 90 | //OnHandleChallenge = async (context) => 91 | //{ 92 | // // custom code to handle authentication challenge unauthorized response. 93 | // context.Response.StatusCode = StatusCodes.Status401Unauthorized; 94 | // context.Response.Headers.Add("ChallengeCustomHeader", "From OnHandleChallenge"); 95 | // await context.Response.WriteAsync("{\"CustomBody\":\"From OnHandleChallenge\"}"); 96 | // context.Handled(); // important! do not forget to call this method at the end. 97 | //}, 98 | 99 | //// A delegate assigned to this property will be invoked if Authorization fails and results in a Forbidden response. 100 | //OnHandleForbidden = async (context) => 101 | //{ 102 | // // custom code to handle forbidden response. 103 | // context.Response.StatusCode = StatusCodes.Status403Forbidden; 104 | // context.Response.Headers.Add("ForbidCustomHeader", "From OnHandleForbidden"); 105 | // await context.Response.WriteAsync("{\"CustomBody\":\"From OnHandleForbidden\"}"); 106 | // context.Handled(); // important! do not forget to call this method at the end. 107 | //}, 108 | 109 | //// A delegate assigned to this property will be invoked when the authentication succeeds. It will not be called if OnValidateKey delegate is assigned. 110 | //// It can be used for adding claims, headers, etc to the response. 111 | //OnAuthenticationSucceeded = (context) => 112 | //{ 113 | // //custom code to add extra bits to the success response. 114 | // context.Response.Headers.Add("SuccessCustomHeader", "From OnAuthenticationSucceeded"); 115 | // var customClaims = new List 116 | // { 117 | // new Claim("CustomClaimType", "Custom Claim Value - from OnAuthenticationSucceeded") 118 | // }; 119 | // context.AddClaims(customClaims); 120 | // //or can add like this - context.Principal.AddIdentity(new ClaimsIdentity(customClaims)); 121 | // return Task.CompletedTask; 122 | //}, 123 | 124 | //// A delegate assigned to this property will be invoked when the authentication fails. 125 | //OnAuthenticationFailed = (context) => 126 | //{ 127 | // // custom code to handle failed authentication. 128 | // context.Fail("Failed to authenticate"); 129 | // return Task.CompletedTask; 130 | //} 131 | 132 | }; 133 | }); 134 | 135 | builder.Services.AddControllers(options => 136 | { 137 | // ALWAYS USE HTTPS (SSL) protocol in production when using ApiKey authentication. 138 | //options.Filters.Add(); 139 | 140 | }); //.AddXmlSerializerFormatters() // To enable XML along with JSON; 141 | 142 | // All the requests will need to be authorized. 143 | // Alternatively, add [Authorize] attribute to Controller or Action Method where necessary. 144 | builder.Services.AddAuthorization(options => 145 | { 146 | options.FallbackPolicy = new AuthorizationPolicyBuilder() 147 | .RequireAuthenticatedUser() 148 | .Build(); 149 | }); 150 | 151 | var app = builder.Build(); 152 | 153 | // Configure the HTTP request pipeline. 154 | 155 | app.UseHttpsRedirection(); 156 | 157 | app.UseAuthentication(); // NOTE: DEFAULT TEMPLATE DOES NOT HAVE THIS, THIS LINE IS REQUIRED AND HAS TO BE ADDED!!! 158 | 159 | app.UseAuthorization(); 160 | 161 | app.MapControllers(); 162 | 163 | app.Run(); 164 | -------------------------------------------------------------------------------- /samples/SampleWebApi_6_0/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:3920", 8 | "sslPort": 44304 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "api/values", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "SampleWebApi_6_0": { 21 | "commandName": "Project", 22 | "dotnetRunMessages": true, 23 | "launchBrowser": true, 24 | "launchUrl": "api/values", 25 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/SampleWebApi_6_0/SampleWebApi_6_0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /samples/SampleWebApi_6_0/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/SampleWebApi_6_0/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /samples/SampleWebApi_7_0/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace SampleWebApi_7_0.Controllers 7 | { 8 | [Route("api/[controller]")] 9 | [ApiController] 10 | public class ValuesController : ControllerBase 11 | { 12 | // GET api/values 13 | [AllowAnonymous] 14 | [HttpGet] 15 | public ActionResult> Get() 16 | { 17 | return new string[] { "value1", "value2" }; 18 | } 19 | 20 | [HttpGet("claims")] 21 | public ActionResult Claims() 22 | { 23 | var sb = new StringBuilder(); 24 | foreach (var claim in User.Claims) 25 | { 26 | sb.AppendLine($"{claim.Type}: {claim.Value}"); 27 | } 28 | return sb.ToString(); 29 | } 30 | 31 | [HttpGet("forbid")] 32 | public new IActionResult Forbid() 33 | { 34 | return base.Forbid(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /samples/SampleWebApi_7_0/Program.cs: -------------------------------------------------------------------------------- 1 | using AspNetCore.Authentication.ApiKey; 2 | using Microsoft.AspNetCore.Authorization; 3 | using SampleWebApi.Repositories; 4 | using SampleWebApi.Services; 5 | 6 | var builder = WebApplication.CreateBuilder(args); 7 | 8 | // Add User repository to the dependency container. 9 | builder.Services.AddTransient(); 10 | 11 | // Add the ApiKey scheme authentication here.. 12 | // It requires Realm to be set in the options if SuppressWWWAuthenticateHeader is not set. 13 | // If an implementation of IApiKeyProvider interface is registered in the dependency register as well as OnValidateKey delegete on options.Events is also set then this delegate will be used instead of an implementation of IApiKeyProvider. 14 | builder.Services.AddAuthentication(ApiKeyDefaults.AuthenticationScheme) 15 | 16 | // The below AddApiKeyInHeaderOrQueryParams without type parameter will require OnValidateKey delegete on options.Events to be set unless an implementation of IApiKeyProvider interface is registered in the dependency register. 17 | // Please note if OnValidateKey delegete on options.Events is also set then this delegate will be used instead of ApiKeyProvider.* 18 | //.AddApiKeyInHeaderOrQueryParams(options => 19 | 20 | // The below AddApiKeyInHeaderOrQueryParams with type parameter will add the ApiKeyProvider to the dependency register. 21 | // Please note if OnValidateKey delegete on options.Events is also set then this delegate will be used instead of ApiKeyProvider. 22 | .AddApiKeyInHeaderOrQueryParams(options => 23 | { 24 | options.Realm = "Sample Web API"; 25 | options.KeyName = "X-API-KEY"; 26 | 27 | //// Optional option to suppress the browser login dialog for ajax calls. 28 | //options.SuppressWWWAuthenticateHeader = true; 29 | 30 | //// Optional option to ignore extra check of ApiKey string after it is validated. 31 | //options.ForLegacyIgnoreExtraValidatedApiKeyCheck = true; 32 | 33 | //// Optional option to ignore authentication if AllowAnonumous metadata/filter attribute is added to an endpoint. 34 | //options.IgnoreAuthenticationIfAllowAnonymous = true; 35 | 36 | //// Optional events to override the ApiKey original logic with custom logic. 37 | //// Only use this if you know what you are doing at your own risk. Any of the events can be assigned. 38 | options.Events = new ApiKeyEvents 39 | { 40 | 41 | //// A delegate assigned to this property will be invoked just before validating the api key. 42 | //OnValidateKey = async (context) => 43 | //{ 44 | // // custom code to handle the api key, create principal and call Success method on context. 45 | // var apiKeyRepository = context.HttpContext.RequestServices.GetRequiredService(); 46 | // var apiKey = await apiKeyRepository.GetApiKeyAsync(context.ApiKey); 47 | // var isValid = apiKey != null && apiKey.Key.Equals(context.ApiKey, StringComparison.OrdinalIgnoreCase); 48 | // if (isValid) 49 | // { 50 | // context.Response.Headers.Add("ValidationCustomHeader", "From OnValidateKey"); 51 | // var claims = new[] 52 | // { 53 | // new Claim(ClaimTypes.NameIdentifier, apiKey.OwnerName, ClaimValueTypes.String, context.Options.ClaimsIssuer), 54 | // new Claim(ClaimTypes.Name, apiKey.OwnerName, ClaimValueTypes.String, context.Options.ClaimsIssuer), 55 | // new Claim("CustomClaimType", "Custom Claim Value - from OnValidateKey") 56 | // }; 57 | // context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name)); 58 | // context.Success(); 59 | // } 60 | // else 61 | // { 62 | // context.NoResult(); 63 | // } 64 | //}, 65 | 66 | //// A delegate assigned to this property will be invoked just before validating the api key. 67 | //// NOTE: Same as above delegate but slightly different implementation which will give same result. 68 | //OnValidateKey = async (context) => 69 | //{ 70 | // // custom code to handle the api key, create principal and call Success method on context. 71 | // var apiKeyRepository = context.HttpContext.RequestServices.GetRequiredService(); 72 | // var apiKey = await apiKeyRepository.GetApiKeyAsync(context.ApiKey); 73 | // var isValid = apiKey != null && apiKey.Key.Equals(context.ApiKey, StringComparison.OrdinalIgnoreCase); 74 | // if (isValid) 75 | // { 76 | // context.Response.Headers.Add("ValidationCustomHeader", "From OnValidateKey"); 77 | // var claims = new[] 78 | // { 79 | // new Claim("CustomClaimType", "Custom Claim Value - from OnValidateKey") 80 | // }; 81 | // context.ValidationSucceeded(apiKey.OwnerName, claims); // claims are optional 82 | // } 83 | // else 84 | // { 85 | // context.ValidationFailed(); 86 | // } 87 | //}, 88 | 89 | //// A delegate assigned to this property will be invoked before a challenge is sent back to the caller when handling unauthorized response. 90 | //OnHandleChallenge = async (context) => 91 | //{ 92 | // // custom code to handle authentication challenge unauthorized response. 93 | // context.Response.StatusCode = StatusCodes.Status401Unauthorized; 94 | // context.Response.Headers.Add("ChallengeCustomHeader", "From OnHandleChallenge"); 95 | // await context.Response.WriteAsync("{\"CustomBody\":\"From OnHandleChallenge\"}"); 96 | // context.Handled(); // important! do not forget to call this method at the end. 97 | //}, 98 | 99 | //// A delegate assigned to this property will be invoked if Authorization fails and results in a Forbidden response. 100 | //OnHandleForbidden = async (context) => 101 | //{ 102 | // // custom code to handle forbidden response. 103 | // context.Response.StatusCode = StatusCodes.Status403Forbidden; 104 | // context.Response.Headers.Add("ForbidCustomHeader", "From OnHandleForbidden"); 105 | // await context.Response.WriteAsync("{\"CustomBody\":\"From OnHandleForbidden\"}"); 106 | // context.Handled(); // important! do not forget to call this method at the end. 107 | //}, 108 | 109 | //// A delegate assigned to this property will be invoked when the authentication succeeds. It will not be called if OnValidateKey delegate is assigned. 110 | //// It can be used for adding claims, headers, etc to the response. 111 | //OnAuthenticationSucceeded = (context) => 112 | //{ 113 | // //custom code to add extra bits to the success response. 114 | // context.Response.Headers.Add("SuccessCustomHeader", "From OnAuthenticationSucceeded"); 115 | // var customClaims = new List 116 | // { 117 | // new Claim("CustomClaimType", "Custom Claim Value - from OnAuthenticationSucceeded") 118 | // }; 119 | // context.AddClaims(customClaims); 120 | // //or can add like this - context.Principal.AddIdentity(new ClaimsIdentity(customClaims)); 121 | // return Task.CompletedTask; 122 | //}, 123 | 124 | //// A delegate assigned to this property will be invoked when the authentication fails. 125 | //OnAuthenticationFailed = (context) => 126 | //{ 127 | // // custom code to handle failed authentication. 128 | // context.Fail("Failed to authenticate"); 129 | // return Task.CompletedTask; 130 | //} 131 | 132 | }; 133 | }); 134 | 135 | builder.Services.AddControllers(options => 136 | { 137 | // ALWAYS USE HTTPS (SSL) protocol in production when using ApiKey authentication. 138 | //options.Filters.Add(); 139 | 140 | }); //.AddXmlSerializerFormatters() // To enable XML along with JSON; 141 | 142 | // All the requests will need to be authorized. 143 | // Alternatively, add [Authorize] attribute to Controller or Action Method where necessary. 144 | builder.Services.AddAuthorization(options => 145 | { 146 | options.FallbackPolicy = new AuthorizationPolicyBuilder() 147 | .RequireAuthenticatedUser() 148 | .Build(); 149 | }); 150 | 151 | var app = builder.Build(); 152 | 153 | // Configure the HTTP request pipeline. 154 | 155 | app.UseHttpsRedirection(); 156 | 157 | app.UseAuthentication(); // NOTE: DEFAULT TEMPLATE DOES NOT HAVE THIS, THIS LINE IS REQUIRED AND HAS TO BE ADDED!!! 158 | 159 | app.UseAuthorization(); 160 | 161 | app.MapControllers(); 162 | 163 | app.Run(); 164 | -------------------------------------------------------------------------------- /samples/SampleWebApi_7_0/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:3920", 8 | "sslPort": 44304 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "api/values", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "SampleWebApi_6_0": { 21 | "commandName": "Project", 22 | "dotnetRunMessages": true, 23 | "launchBrowser": true, 24 | "launchUrl": "api/values", 25 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/SampleWebApi_7_0/SampleWebApi_7_0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /samples/SampleWebApi_7_0/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/SampleWebApi_7_0/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /samples/SampleWebApi_8_0/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc; 3 | using System.Text; 4 | 5 | namespace SampleWebApi_8_0.Controllers 6 | { 7 | [Route("api/[controller]")] 8 | [ApiController] 9 | public class ValuesController : ControllerBase 10 | { 11 | // GET api/values 12 | [AllowAnonymous] 13 | [HttpGet] 14 | public ActionResult> Get() 15 | { 16 | return new string[] { "value1", "value2" }; 17 | } 18 | 19 | [HttpGet("claims")] 20 | public ActionResult Claims() 21 | { 22 | var sb = new StringBuilder(); 23 | foreach (var claim in User.Claims) 24 | { 25 | sb.AppendLine($"{claim.Type}: {claim.Value}"); 26 | } 27 | return sb.ToString(); 28 | } 29 | 30 | [HttpGet("forbid")] 31 | public new IActionResult Forbid() 32 | { 33 | return base.Forbid(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /samples/SampleWebApi_8_0/Program.cs: -------------------------------------------------------------------------------- 1 | using AspNetCore.Authentication.ApiKey; 2 | using Microsoft.AspNetCore.Authorization; 3 | using SampleWebApi.Repositories; 4 | using SampleWebApi.Services; 5 | 6 | var builder = WebApplication.CreateBuilder(args); 7 | 8 | // Add User repository to the dependency container. 9 | builder.Services.AddTransient(); 10 | 11 | // Add the ApiKey scheme authentication here.. 12 | // It requires Realm to be set in the options if SuppressWWWAuthenticateHeader is not set. 13 | // If an implementation of IApiKeyProvider interface is registered in the dependency register as well as OnValidateKey delegete on options.Events is also set then this delegate will be used instead of an implementation of IApiKeyProvider. 14 | builder.Services.AddAuthentication(ApiKeyDefaults.AuthenticationScheme) 15 | 16 | // The below AddApiKeyInHeaderOrQueryParams without type parameter will require OnValidateKey delegete on options.Events to be set unless an implementation of IApiKeyProvider interface is registered in the dependency register. 17 | // Please note if OnValidateKey delegete on options.Events is also set then this delegate will be used instead of ApiKeyProvider.* 18 | //.AddApiKeyInHeaderOrQueryParams(options => 19 | 20 | // The below AddApiKeyInHeaderOrQueryParams with type parameter will add the ApiKeyProvider to the dependency register. 21 | // Please note if OnValidateKey delegete on options.Events is also set then this delegate will be used instead of ApiKeyProvider. 22 | .AddApiKeyInHeaderOrQueryParams(options => 23 | { 24 | options.Realm = "Sample Web API"; 25 | options.KeyName = "X-API-KEY"; 26 | 27 | //// Optional option to suppress the browser login dialog for ajax calls. 28 | //options.SuppressWWWAuthenticateHeader = true; 29 | 30 | //// Optional option to ignore extra check of ApiKey string after it is validated. 31 | //options.ForLegacyIgnoreExtraValidatedApiKeyCheck = true; 32 | 33 | //// Optional option to ignore authentication if AllowAnonumous metadata/filter attribute is added to an endpoint. 34 | //options.IgnoreAuthenticationIfAllowAnonymous = true; 35 | 36 | //// Optional events to override the ApiKey original logic with custom logic. 37 | //// Only use this if you know what you are doing at your own risk. Any of the events can be assigned. 38 | options.Events = new ApiKeyEvents 39 | { 40 | 41 | //// A delegate assigned to this property will be invoked just before validating the api key. 42 | //OnValidateKey = async (context) => 43 | //{ 44 | // // custom code to handle the api key, create principal and call Success method on context. 45 | // var apiKeyRepository = context.HttpContext.RequestServices.GetRequiredService(); 46 | // var apiKey = await apiKeyRepository.GetApiKeyAsync(context.ApiKey); 47 | // var isValid = apiKey != null && apiKey.Key.Equals(context.ApiKey, StringComparison.OrdinalIgnoreCase); 48 | // if (isValid) 49 | // { 50 | // context.Response.Headers.Add("ValidationCustomHeader", "From OnValidateKey"); 51 | // var claims = new[] 52 | // { 53 | // new Claim(ClaimTypes.NameIdentifier, apiKey.OwnerName, ClaimValueTypes.String, context.Options.ClaimsIssuer), 54 | // new Claim(ClaimTypes.Name, apiKey.OwnerName, ClaimValueTypes.String, context.Options.ClaimsIssuer), 55 | // new Claim("CustomClaimType", "Custom Claim Value - from OnValidateKey") 56 | // }; 57 | // context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name)); 58 | // context.Success(); 59 | // } 60 | // else 61 | // { 62 | // context.NoResult(); 63 | // } 64 | //}, 65 | 66 | //// A delegate assigned to this property will be invoked just before validating the api key. 67 | //// NOTE: Same as above delegate but slightly different implementation which will give same result. 68 | //OnValidateKey = async (context) => 69 | //{ 70 | // // custom code to handle the api key, create principal and call Success method on context. 71 | // var apiKeyRepository = context.HttpContext.RequestServices.GetRequiredService(); 72 | // var apiKey = await apiKeyRepository.GetApiKeyAsync(context.ApiKey); 73 | // var isValid = apiKey != null && apiKey.Key.Equals(context.ApiKey, StringComparison.OrdinalIgnoreCase); 74 | // if (isValid) 75 | // { 76 | // context.Response.Headers.Add("ValidationCustomHeader", "From OnValidateKey"); 77 | // var claims = new[] 78 | // { 79 | // new Claim("CustomClaimType", "Custom Claim Value - from OnValidateKey") 80 | // }; 81 | // context.ValidationSucceeded(apiKey.OwnerName, claims); // claims are optional 82 | // } 83 | // else 84 | // { 85 | // context.ValidationFailed(); 86 | // } 87 | //}, 88 | 89 | //// A delegate assigned to this property will be invoked before a challenge is sent back to the caller when handling unauthorized response. 90 | //OnHandleChallenge = async (context) => 91 | //{ 92 | // // custom code to handle authentication challenge unauthorized response. 93 | // context.Response.StatusCode = StatusCodes.Status401Unauthorized; 94 | // context.Response.Headers.Add("ChallengeCustomHeader", "From OnHandleChallenge"); 95 | // await context.Response.WriteAsync("{\"CustomBody\":\"From OnHandleChallenge\"}"); 96 | // context.Handled(); // important! do not forget to call this method at the end. 97 | //}, 98 | 99 | //// A delegate assigned to this property will be invoked if Authorization fails and results in a Forbidden response. 100 | //OnHandleForbidden = async (context) => 101 | //{ 102 | // // custom code to handle forbidden response. 103 | // context.Response.StatusCode = StatusCodes.Status403Forbidden; 104 | // context.Response.Headers.Add("ForbidCustomHeader", "From OnHandleForbidden"); 105 | // await context.Response.WriteAsync("{\"CustomBody\":\"From OnHandleForbidden\"}"); 106 | // context.Handled(); // important! do not forget to call this method at the end. 107 | //}, 108 | 109 | //// A delegate assigned to this property will be invoked when the authentication succeeds. It will not be called if OnValidateKey delegate is assigned. 110 | //// It can be used for adding claims, headers, etc to the response. 111 | //OnAuthenticationSucceeded = (context) => 112 | //{ 113 | // //custom code to add extra bits to the success response. 114 | // context.Response.Headers.Add("SuccessCustomHeader", "From OnAuthenticationSucceeded"); 115 | // var customClaims = new List 116 | // { 117 | // new Claim("CustomClaimType", "Custom Claim Value - from OnAuthenticationSucceeded") 118 | // }; 119 | // context.AddClaims(customClaims); 120 | // //or can add like this - context.Principal.AddIdentity(new ClaimsIdentity(customClaims)); 121 | // return Task.CompletedTask; 122 | //}, 123 | 124 | //// A delegate assigned to this property will be invoked when the authentication fails. 125 | //OnAuthenticationFailed = (context) => 126 | //{ 127 | // // custom code to handle failed authentication. 128 | // context.Fail("Failed to authenticate"); 129 | // return Task.CompletedTask; 130 | //} 131 | 132 | }; 133 | }); 134 | 135 | builder.Services.AddControllers(options => 136 | { 137 | // ALWAYS USE HTTPS (SSL) protocol in production when using ApiKey authentication. 138 | //options.Filters.Add(); 139 | 140 | }); //.AddXmlSerializerFormatters() // To enable XML along with JSON; 141 | 142 | // All the requests will need to be authorized. 143 | // Alternatively, add [Authorize] attribute to Controller or Action Method where necessary. 144 | builder.Services.AddAuthorizationBuilder() 145 | .AddFallbackPolicy( 146 | "FallbackPolicy", 147 | new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build() 148 | ); 149 | 150 | var app = builder.Build(); 151 | 152 | // Configure the HTTP request pipeline. 153 | 154 | app.UseHttpsRedirection(); 155 | 156 | app.UseAuthentication(); // NOTE: DEFAULT TEMPLATE DOES NOT HAVE THIS, THIS LINE IS REQUIRED AND HAS TO BE ADDED!!! 157 | 158 | app.UseAuthorization(); 159 | 160 | app.MapControllers(); 161 | 162 | app.Run(); 163 | -------------------------------------------------------------------------------- /samples/SampleWebApi_8_0/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:3920", 8 | "sslPort": 44304 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "api/values", 17 | "applicationUrl": "http://localhost:5000", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "https": { 23 | "commandName": "Project", 24 | "dotnetRunMessages": true, 25 | "launchBrowser": true, 26 | "launchUrl": "api/values", 27 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 28 | "environmentVariables": { 29 | "ASPNETCORE_ENVIRONMENT": "Development" 30 | } 31 | }, 32 | "IIS Express": { 33 | "commandName": "IISExpress", 34 | "launchBrowser": true, 35 | "launchUrl": "api/values", 36 | "environmentVariables": { 37 | "ASPNETCORE_ENVIRONMENT": "Development" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /samples/SampleWebApi_8_0/SampleWebApi_8_0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /samples/SampleWebApi_8_0/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/SampleWebApi_8_0/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /samples/SampleWebApi_9_0/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc; 3 | using System.Text; 4 | 5 | namespace SampleWebApi_9_0.Controllers 6 | { 7 | [Route("api/[controller]")] 8 | [ApiController] 9 | public class ValuesController : ControllerBase 10 | { 11 | // GET api/values 12 | [AllowAnonymous] 13 | [HttpGet] 14 | public ActionResult> Get() 15 | { 16 | return new string[] { "value1", "value2" }; 17 | } 18 | 19 | [HttpGet("claims")] 20 | public ActionResult Claims() 21 | { 22 | var sb = new StringBuilder(); 23 | foreach (var claim in User.Claims) 24 | { 25 | sb.AppendLine($"{claim.Type}: {claim.Value}"); 26 | } 27 | return sb.ToString(); 28 | } 29 | 30 | [HttpGet("forbid")] 31 | public new IActionResult Forbid() 32 | { 33 | return base.Forbid(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /samples/SampleWebApi_9_0/Program.cs: -------------------------------------------------------------------------------- 1 | using AspNetCore.Authentication.ApiKey; 2 | using Microsoft.AspNetCore.Authorization; 3 | using SampleWebApi.Repositories; 4 | using SampleWebApi.Services; 5 | 6 | var builder = WebApplication.CreateBuilder(args); 7 | 8 | // Add User repository to the dependency container. 9 | builder.Services.AddTransient(); 10 | 11 | // Add the ApiKey scheme authentication here.. 12 | // It requires Realm to be set in the options if SuppressWWWAuthenticateHeader is not set. 13 | // If an implementation of IApiKeyProvider interface is registered in the dependency register as well as OnValidateKey delegete on options.Events is also set then this delegate will be used instead of an implementation of IApiKeyProvider. 14 | builder.Services.AddAuthentication(ApiKeyDefaults.AuthenticationScheme) 15 | 16 | // The below AddApiKeyInHeaderOrQueryParams without type parameter will require OnValidateKey delegete on options.Events to be set unless an implementation of IApiKeyProvider interface is registered in the dependency register. 17 | // Please note if OnValidateKey delegete on options.Events is also set then this delegate will be used instead of ApiKeyProvider.* 18 | //.AddApiKeyInHeaderOrQueryParams(options => 19 | 20 | // The below AddApiKeyInHeaderOrQueryParams with type parameter will add the ApiKeyProvider to the dependency register. 21 | // Please note if OnValidateKey delegete on options.Events is also set then this delegate will be used instead of ApiKeyProvider. 22 | .AddApiKeyInHeaderOrQueryParams(options => 23 | { 24 | options.Realm = "Sample Web API"; 25 | options.KeyName = "X-API-KEY"; 26 | 27 | //// Optional option to suppress the browser login dialog for ajax calls. 28 | //options.SuppressWWWAuthenticateHeader = true; 29 | 30 | //// Optional option to ignore extra check of ApiKey string after it is validated. 31 | //options.ForLegacyIgnoreExtraValidatedApiKeyCheck = true; 32 | 33 | //// Optional option to ignore authentication if AllowAnonumous metadata/filter attribute is added to an endpoint. 34 | //options.IgnoreAuthenticationIfAllowAnonymous = true; 35 | 36 | //// Optional events to override the ApiKey original logic with custom logic. 37 | //// Only use this if you know what you are doing at your own risk. Any of the events can be assigned. 38 | options.Events = new ApiKeyEvents 39 | { 40 | 41 | //// A delegate assigned to this property will be invoked just before validating the api key. 42 | //OnValidateKey = async (context) => 43 | //{ 44 | // // custom code to handle the api key, create principal and call Success method on context. 45 | // var apiKeyRepository = context.HttpContext.RequestServices.GetRequiredService(); 46 | // var apiKey = await apiKeyRepository.GetApiKeyAsync(context.ApiKey); 47 | // var isValid = apiKey != null && apiKey.Key.Equals(context.ApiKey, StringComparison.OrdinalIgnoreCase); 48 | // if (isValid) 49 | // { 50 | // context.Response.Headers.Add("ValidationCustomHeader", "From OnValidateKey"); 51 | // var claims = new[] 52 | // { 53 | // new Claim(ClaimTypes.NameIdentifier, apiKey.OwnerName, ClaimValueTypes.String, context.Options.ClaimsIssuer), 54 | // new Claim(ClaimTypes.Name, apiKey.OwnerName, ClaimValueTypes.String, context.Options.ClaimsIssuer), 55 | // new Claim("CustomClaimType", "Custom Claim Value - from OnValidateKey") 56 | // }; 57 | // context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name)); 58 | // context.Success(); 59 | // } 60 | // else 61 | // { 62 | // context.NoResult(); 63 | // } 64 | //}, 65 | 66 | //// A delegate assigned to this property will be invoked just before validating the api key. 67 | //// NOTE: Same as above delegate but slightly different implementation which will give same result. 68 | //OnValidateKey = async (context) => 69 | //{ 70 | // // custom code to handle the api key, create principal and call Success method on context. 71 | // var apiKeyRepository = context.HttpContext.RequestServices.GetRequiredService(); 72 | // var apiKey = await apiKeyRepository.GetApiKeyAsync(context.ApiKey); 73 | // var isValid = apiKey != null && apiKey.Key.Equals(context.ApiKey, StringComparison.OrdinalIgnoreCase); 74 | // if (isValid) 75 | // { 76 | // context.Response.Headers.Add("ValidationCustomHeader", "From OnValidateKey"); 77 | // var claims = new[] 78 | // { 79 | // new Claim("CustomClaimType", "Custom Claim Value - from OnValidateKey") 80 | // }; 81 | // context.ValidationSucceeded(apiKey.OwnerName, claims); // claims are optional 82 | // } 83 | // else 84 | // { 85 | // context.ValidationFailed(); 86 | // } 87 | //}, 88 | 89 | //// A delegate assigned to this property will be invoked before a challenge is sent back to the caller when handling unauthorized response. 90 | //OnHandleChallenge = async (context) => 91 | //{ 92 | // // custom code to handle authentication challenge unauthorized response. 93 | // context.Response.StatusCode = StatusCodes.Status401Unauthorized; 94 | // context.Response.Headers.Add("ChallengeCustomHeader", "From OnHandleChallenge"); 95 | // await context.Response.WriteAsync("{\"CustomBody\":\"From OnHandleChallenge\"}"); 96 | // context.Handled(); // important! do not forget to call this method at the end. 97 | //}, 98 | 99 | //// A delegate assigned to this property will be invoked if Authorization fails and results in a Forbidden response. 100 | //OnHandleForbidden = async (context) => 101 | //{ 102 | // // custom code to handle forbidden response. 103 | // context.Response.StatusCode = StatusCodes.Status403Forbidden; 104 | // context.Response.Headers.Add("ForbidCustomHeader", "From OnHandleForbidden"); 105 | // await context.Response.WriteAsync("{\"CustomBody\":\"From OnHandleForbidden\"}"); 106 | // context.Handled(); // important! do not forget to call this method at the end. 107 | //}, 108 | 109 | //// A delegate assigned to this property will be invoked when the authentication succeeds. It will not be called if OnValidateKey delegate is assigned. 110 | //// It can be used for adding claims, headers, etc to the response. 111 | //OnAuthenticationSucceeded = (context) => 112 | //{ 113 | // //custom code to add extra bits to the success response. 114 | // context.Response.Headers.Add("SuccessCustomHeader", "From OnAuthenticationSucceeded"); 115 | // var customClaims = new List 116 | // { 117 | // new Claim("CustomClaimType", "Custom Claim Value - from OnAuthenticationSucceeded") 118 | // }; 119 | // context.AddClaims(customClaims); 120 | // //or can add like this - context.Principal.AddIdentity(new ClaimsIdentity(customClaims)); 121 | // return Task.CompletedTask; 122 | //}, 123 | 124 | //// A delegate assigned to this property will be invoked when the authentication fails. 125 | //OnAuthenticationFailed = (context) => 126 | //{ 127 | // // custom code to handle failed authentication. 128 | // context.Fail("Failed to authenticate"); 129 | // return Task.CompletedTask; 130 | //} 131 | 132 | }; 133 | }); 134 | 135 | builder.Services.AddControllers(options => 136 | { 137 | // ALWAYS USE HTTPS (SSL) protocol in production when using ApiKey authentication. 138 | //options.Filters.Add(); 139 | 140 | }); //.AddXmlSerializerFormatters() // To enable XML along with JSON; 141 | 142 | // All the requests will need to be authorized. 143 | // Alternatively, add [Authorize] attribute to Controller or Action Method where necessary. 144 | builder.Services.AddAuthorizationBuilder() 145 | .AddFallbackPolicy( 146 | "FallbackPolicy", 147 | new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build() 148 | ); 149 | 150 | var app = builder.Build(); 151 | 152 | // Configure the HTTP request pipeline. 153 | 154 | app.UseHttpsRedirection(); 155 | 156 | app.UseAuthentication(); // NOTE: DEFAULT TEMPLATE DOES NOT HAVE THIS, THIS LINE IS REQUIRED AND HAS TO BE ADDED!!! 157 | 158 | app.UseAuthorization(); 159 | 160 | app.MapControllers(); 161 | 162 | app.Run(); 163 | 164 | -------------------------------------------------------------------------------- /samples/SampleWebApi_9_0/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:3920", 8 | "sslPort": 44304 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "api/values", 17 | "applicationUrl": "http://localhost:5000", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "https": { 23 | "commandName": "Project", 24 | "dotnetRunMessages": true, 25 | "launchBrowser": true, 26 | "launchUrl": "api/values", 27 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 28 | "environmentVariables": { 29 | "ASPNETCORE_ENVIRONMENT": "Development" 30 | } 31 | }, 32 | "IIS Express": { 33 | "commandName": "IISExpress", 34 | "launchBrowser": true, 35 | "launchUrl": "api/values", 36 | "environmentVariables": { 37 | "ASPNETCORE_ENVIRONMENT": "Development" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /samples/SampleWebApi_9_0/SampleWebApi_9_0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /samples/SampleWebApi_9_0/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/SampleWebApi_9_0/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /samples/SampleWebApi_AOT/Program.cs: -------------------------------------------------------------------------------- 1 | using AspNetCore.Authentication.ApiKey; 2 | using Microsoft.AspNetCore.Authentication; 3 | using Microsoft.AspNetCore.Authorization; 4 | using SampleWebApi.Repositories; 5 | using SampleWebApi.Services; 6 | using System.Text; 7 | using System.Text.Json.Serialization; 8 | 9 | var builder = WebApplication.CreateSlimBuilder(args); 10 | builder.WebHost.UseKestrelHttpsConfiguration(); 11 | 12 | // Add User repository to the dependency container. 13 | builder.Services.AddTransient(); 14 | 15 | // Add the ApiKey scheme authentication here.. 16 | // It requires Realm to be set in the options if SuppressWWWAuthenticateHeader is not set. 17 | // If an implementation of IApiKeyProvider interface is registered in the dependency register as well as OnValidateKey delegete on options.Events is also set then this delegate will be used instead of an implementation of IApiKeyProvider. 18 | builder.Services.AddAuthentication(ApiKeyDefaults.AuthenticationScheme) 19 | 20 | // The below AddApiKeyInHeaderOrQueryParams without type parameter will require OnValidateKey delegete on options.Events to be set unless an implementation of IApiKeyProvider interface is registered in the dependency register. 21 | // Please note if OnValidateKey delegete on options.Events is also set then this delegate will be used instead of ApiKeyProvider.* 22 | //.AddApiKeyInHeaderOrQueryParams(options => 23 | 24 | // The below AddApiKeyInHeaderOrQueryParams with type parameter will add the ApiKeyProvider to the dependency register. 25 | // Please note if OnValidateKey delegete on options.Events is also set then this delegate will be used instead of ApiKeyProvider. 26 | .AddApiKeyInHeaderOrQueryParams(options => 27 | { 28 | options.Realm = "Sample Web API"; 29 | options.KeyName = "X-API-KEY"; 30 | 31 | //// Optional option to suppress the browser login dialog for ajax calls. 32 | //options.SuppressWWWAuthenticateHeader = true; 33 | 34 | //// Optional option to ignore extra check of ApiKey string after it is validated. 35 | //options.ForLegacyIgnoreExtraValidatedApiKeyCheck = true; 36 | 37 | //// Optional option to ignore authentication if AllowAnonumous metadata/filter attribute is added to an endpoint. 38 | //options.IgnoreAuthenticationIfAllowAnonymous = true; 39 | 40 | //// Optional events to override the ApiKey original logic with custom logic. 41 | //// Only use this if you know what you are doing at your own risk. Any of the events can be assigned. 42 | options.Events = new ApiKeyEvents 43 | { 44 | 45 | //// A delegate assigned to this property will be invoked just before validating the api key. 46 | //OnValidateKey = async (context) => 47 | //{ 48 | // // custom code to handle the api key, create principal and call Success method on context. 49 | // var apiKeyRepository = context.HttpContext.RequestServices.GetRequiredService(); 50 | // var apiKey = await apiKeyRepository.GetApiKeyAsync(context.ApiKey); 51 | // var isValid = apiKey != null && apiKey.Key.Equals(context.ApiKey, StringComparison.OrdinalIgnoreCase); 52 | // if (isValid) 53 | // { 54 | // context.Response.Headers.Add("ValidationCustomHeader", "From OnValidateKey"); 55 | // var claims = new[] 56 | // { 57 | // new Claim(ClaimTypes.NameIdentifier, apiKey.OwnerName, ClaimValueTypes.String, context.Options.ClaimsIssuer), 58 | // new Claim(ClaimTypes.Name, apiKey.OwnerName, ClaimValueTypes.String, context.Options.ClaimsIssuer), 59 | // new Claim("CustomClaimType", "Custom Claim Value - from OnValidateKey") 60 | // }; 61 | // context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name)); 62 | // context.Success(); 63 | // } 64 | // else 65 | // { 66 | // context.NoResult(); 67 | // } 68 | //}, 69 | 70 | //// A delegate assigned to this property will be invoked just before validating the api key. 71 | //// NOTE: Same as above delegate but slightly different implementation which will give same result. 72 | //OnValidateKey = async (context) => 73 | //{ 74 | // // custom code to handle the api key, create principal and call Success method on context. 75 | // var apiKeyRepository = context.HttpContext.RequestServices.GetRequiredService(); 76 | // var apiKey = await apiKeyRepository.GetApiKeyAsync(context.ApiKey); 77 | // var isValid = apiKey != null && apiKey.Key.Equals(context.ApiKey, StringComparison.OrdinalIgnoreCase); 78 | // if (isValid) 79 | // { 80 | // context.Response.Headers.Add("ValidationCustomHeader", "From OnValidateKey"); 81 | // var claims = new[] 82 | // { 83 | // new Claim("CustomClaimType", "Custom Claim Value - from OnValidateKey") 84 | // }; 85 | // context.ValidationSucceeded(apiKey.OwnerName, claims); // claims are optional 86 | // } 87 | // else 88 | // { 89 | // context.ValidationFailed(); 90 | // } 91 | //}, 92 | 93 | //// A delegate assigned to this property will be invoked before a challenge is sent back to the caller when handling unauthorized response. 94 | //OnHandleChallenge = async (context) => 95 | //{ 96 | // // custom code to handle authentication challenge unauthorized response. 97 | // context.Response.StatusCode = StatusCodes.Status401Unauthorized; 98 | // context.Response.Headers.Add("ChallengeCustomHeader", "From OnHandleChallenge"); 99 | // await context.Response.WriteAsync("{\"CustomBody\":\"From OnHandleChallenge\"}"); 100 | // context.Handled(); // important! do not forget to call this method at the end. 101 | //}, 102 | 103 | //// A delegate assigned to this property will be invoked if Authorization fails and results in a Forbidden response. 104 | //OnHandleForbidden = async (context) => 105 | //{ 106 | // // custom code to handle forbidden response. 107 | // context.Response.StatusCode = StatusCodes.Status403Forbidden; 108 | // context.Response.Headers.Add("ForbidCustomHeader", "From OnHandleForbidden"); 109 | // await context.Response.WriteAsync("{\"CustomBody\":\"From OnHandleForbidden\"}"); 110 | // context.Handled(); // important! do not forget to call this method at the end. 111 | //}, 112 | 113 | //// A delegate assigned to this property will be invoked when the authentication succeeds. It will not be called if OnValidateKey delegate is assigned. 114 | //// It can be used for adding claims, headers, etc to the response. 115 | //OnAuthenticationSucceeded = (context) => 116 | //{ 117 | // //custom code to add extra bits to the success response. 118 | // context.Response.Headers.Add("SuccessCustomHeader", "From OnAuthenticationSucceeded"); 119 | // var customClaims = new List 120 | // { 121 | // new Claim("CustomClaimType", "Custom Claim Value - from OnAuthenticationSucceeded") 122 | // }; 123 | // context.AddClaims(customClaims); 124 | // //or can add like this - context.Principal.AddIdentity(new ClaimsIdentity(customClaims)); 125 | // return Task.CompletedTask; 126 | //}, 127 | 128 | //// A delegate assigned to this property will be invoked when the authentication fails. 129 | //OnAuthenticationFailed = (context) => 130 | //{ 131 | // // custom code to handle failed authentication. 132 | // context.Fail("Failed to authenticate"); 133 | // return Task.CompletedTask; 134 | //} 135 | 136 | }; 137 | }); 138 | 139 | // All the requests will need to be authorized. 140 | // Alternatively, add [Authorize] attribute to Controller or Action Method where necessary. 141 | builder.Services.AddAuthorizationBuilder() 142 | .AddFallbackPolicy( 143 | "FallbackPolicy", 144 | new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build() 145 | ); 146 | 147 | builder.Services.ConfigureHttpJsonOptions(options => 148 | { 149 | options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); 150 | }); 151 | 152 | var app = builder.Build(); 153 | 154 | app.UseHttpsRedirection(); 155 | 156 | app.UseAuthentication(); // NOTE: DEFAULT TEMPLATE DOES NOT HAVE THIS, THIS LINE IS REQUIRED AND HAS TO BE ADDED!!! 157 | 158 | app.UseAuthorization(); 159 | 160 | var valuesApi = app.MapGroup("/api/values") 161 | .RequireAuthorization(); 162 | valuesApi.MapGet("/", () => new[] { "value1", "value2" }) 163 | .AllowAnonymous(); 164 | valuesApi.MapGet("/claims", async (context) => 165 | { 166 | var sb = new StringBuilder(); 167 | foreach (var claim in context.User.Claims) 168 | { 169 | sb.AppendLine($"{claim.Type}: {claim.Value}"); 170 | } 171 | context.Response.StatusCode = 200; 172 | await context.Response.WriteAsync(sb.ToString()); 173 | }); 174 | valuesApi.MapGet("/forbid", async (context) => 175 | { 176 | await context.ForbidAsync(); 177 | }); 178 | 179 | app.Run(); 180 | 181 | 182 | [JsonSerializable(typeof(string))] 183 | [JsonSerializable(typeof(string[]))] 184 | internal partial class AppJsonSerializerContext : JsonSerializerContext 185 | { 186 | 187 | } -------------------------------------------------------------------------------- /samples/SampleWebApi_AOT/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "https": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "launchBrowser": true, 8 | "launchUrl": "api/values", 9 | "applicationUrl": "https://localhost:44304;http://localhost:3920", 10 | "sslPort": 44304, 11 | "useSSL": true, 12 | "environmentVariables": { 13 | "ASPNETCORE_ENVIRONMENT": "Development" 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/SampleWebApi_AOT/SampleWebApi_AOT.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | true 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /samples/SampleWebApi_AOT/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/SampleWebApi_AOT/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /src/AspNetCore.Authentication.ApiKey/ApiKeyDefaults.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | namespace AspNetCore.Authentication.ApiKey 5 | { 6 | /// 7 | /// Default values used by api key authentication. 8 | /// 9 | public static class ApiKeyDefaults 10 | { 11 | /// 12 | /// Default value for AuthenticationScheme 13 | /// 14 | public const string AuthenticationScheme = "ApiKey"; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/AspNetCore.Authentication.ApiKey/ApiKeyInAuthorizationHeaderHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using Microsoft.AspNetCore.Authentication; 5 | using Microsoft.Extensions.Logging; 6 | using Microsoft.Extensions.Options; 7 | using Microsoft.Extensions.Primitives; 8 | using Microsoft.Net.Http.Headers; 9 | using System.Net.Http.Headers; 10 | using System.Text.Encodings.Web; 11 | 12 | namespace AspNetCore.Authentication.ApiKey 13 | { 14 | public class ApiKeyInAuthorizationHeaderHandler : ApiKeyHandlerBase 15 | { 16 | private const string WwwAuthenticateInParameter = "authorization_header"; 17 | protected override string GetWwwAuthenticateInParameter() => WwwAuthenticateInParameter; 18 | 19 | #if NET8_0_OR_GREATER 20 | protected ApiKeyInAuthorizationHeaderHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) 21 | : base(options, logger, encoder) 22 | { 23 | } 24 | 25 | [Obsolete("ISystemClock is obsolete, use TimeProvider on AuthenticationSchemeOptions instead.")] 26 | #endif 27 | public ApiKeyInAuthorizationHeaderHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) 28 | : base(options, logger, encoder, clock) 29 | { 30 | } 31 | 32 | protected override Task ParseApiKeyAsync() 33 | { 34 | if (Request.Headers.TryGetValue(HeaderNames.Authorization, out StringValues value) 35 | && AuthenticationHeaderValue.TryParse(value, out var headerValue) 36 | && (headerValue.Scheme.Equals(Scheme.Name, StringComparison.OrdinalIgnoreCase) 37 | || headerValue.Scheme.Equals(Options.KeyName, StringComparison.OrdinalIgnoreCase) 38 | ) 39 | ) 40 | { 41 | return Task.FromResult(headerValue.Parameter ?? string.Empty); 42 | } 43 | 44 | // No ApiKey found 45 | return Task.FromResult(string.Empty); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/AspNetCore.Authentication.ApiKey/ApiKeyInHeaderHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using Microsoft.AspNetCore.Authentication; 5 | using Microsoft.Extensions.Logging; 6 | using Microsoft.Extensions.Options; 7 | using System.Text.Encodings.Web; 8 | 9 | namespace AspNetCore.Authentication.ApiKey 10 | { 11 | public class ApiKeyInHeaderHandler : ApiKeyHandlerBase 12 | { 13 | private const string WwwAuthenticateInParameter = "header"; 14 | protected override string GetWwwAuthenticateInParameter() => WwwAuthenticateInParameter; 15 | 16 | #if NET8_0_OR_GREATER 17 | protected ApiKeyInHeaderHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) 18 | : base(options, logger, encoder) 19 | { 20 | } 21 | 22 | [Obsolete("ISystemClock is obsolete, use TimeProvider on AuthenticationSchemeOptions instead.")] 23 | #endif 24 | public ApiKeyInHeaderHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) 25 | : base(options, logger, encoder, clock) 26 | { 27 | } 28 | 29 | protected override Task ParseApiKeyAsync() 30 | { 31 | if (Request.Headers.TryGetValue(Options.KeyName, out var value)) 32 | { 33 | return Task.FromResult(value.FirstOrDefault() ?? string.Empty); 34 | } 35 | 36 | // No ApiKey found 37 | return Task.FromResult(string.Empty); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/AspNetCore.Authentication.ApiKey/ApiKeyInHeaderOrQueryParamsHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using Microsoft.AspNetCore.Authentication; 5 | using Microsoft.Extensions.Logging; 6 | using Microsoft.Extensions.Options; 7 | using Microsoft.Net.Http.Headers; 8 | using System.Net.Http.Headers; 9 | using System.Text.Encodings.Web; 10 | 11 | namespace AspNetCore.Authentication.ApiKey 12 | { 13 | public class ApiKeyInHeaderOrQueryParamsHandler : ApiKeyHandlerBase 14 | { 15 | private const string WwwAuthenticateInParameter = "header_or_query_params"; 16 | protected override string GetWwwAuthenticateInParameter() => WwwAuthenticateInParameter; 17 | 18 | #if NET8_0_OR_GREATER 19 | protected ApiKeyInHeaderOrQueryParamsHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) 20 | : base(options, logger, encoder) 21 | { 22 | } 23 | 24 | [Obsolete("ISystemClock is obsolete, use TimeProvider on AuthenticationSchemeOptions instead.")] 25 | #endif 26 | public ApiKeyInHeaderOrQueryParamsHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) 27 | : base(options, logger, encoder, clock) 28 | { 29 | } 30 | 31 | protected override Task ParseApiKeyAsync() 32 | { 33 | // Try query parameter 34 | if (Request.Query.TryGetValue(Options.KeyName, out var value)) 35 | { 36 | return Task.FromResult(value.FirstOrDefault() ?? string.Empty); 37 | } 38 | 39 | // No ApiKey query parameter found try headers 40 | if (Request.Headers.TryGetValue(Options.KeyName, out var headerValue)) 41 | { 42 | return Task.FromResult(headerValue.FirstOrDefault() ?? string.Empty); 43 | } 44 | 45 | // No ApiKey query parameter or header found then try Authorization header 46 | if (Request.Headers.TryGetValue(HeaderNames.Authorization, out Microsoft.Extensions.Primitives.StringValues authHeaderStringValue) 47 | && AuthenticationHeaderValue.TryParse(authHeaderStringValue, out var authHeaderValue) 48 | && authHeaderValue.Scheme.Equals(Options.KeyName, StringComparison.OrdinalIgnoreCase) 49 | ) 50 | { 51 | return Task.FromResult(authHeaderValue.Parameter ?? string.Empty); 52 | } 53 | 54 | // No ApiKey found 55 | return Task.FromResult(string.Empty); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/AspNetCore.Authentication.ApiKey/ApiKeyInQueryParamsHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using Microsoft.AspNetCore.Authentication; 5 | using Microsoft.Extensions.Logging; 6 | using Microsoft.Extensions.Options; 7 | using System.Text.Encodings.Web; 8 | 9 | namespace AspNetCore.Authentication.ApiKey 10 | { 11 | public class ApiKeyInQueryParamsHandler : ApiKeyHandlerBase 12 | { 13 | private const string WwwAuthenticateInParameter = "query_params"; 14 | protected override string GetWwwAuthenticateInParameter() => WwwAuthenticateInParameter; 15 | 16 | #if NET8_0_OR_GREATER 17 | protected ApiKeyInQueryParamsHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) 18 | : base(options, logger, encoder) 19 | { 20 | } 21 | 22 | [Obsolete("ISystemClock is obsolete, use TimeProvider on AuthenticationSchemeOptions instead.")] 23 | #endif 24 | public ApiKeyInQueryParamsHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) 25 | : base(options, logger, encoder, clock) 26 | { 27 | } 28 | 29 | protected override Task ParseApiKeyAsync() 30 | { 31 | if (Request.Query.TryGetValue(Options.KeyName, out var value)) 32 | { 33 | return Task.FromResult(value.FirstOrDefault() ?? string.Empty); 34 | } 35 | 36 | // No ApiKey query parameter found 37 | return Task.FromResult(string.Empty); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/AspNetCore.Authentication.ApiKey/ApiKeyInRouteValuesHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | #if NETCOREAPP3_0_OR_GREATER 4 | 5 | using Microsoft.AspNetCore.Authentication; 6 | using Microsoft.Extensions.Logging; 7 | using Microsoft.Extensions.Options; 8 | using System.Text.Encodings.Web; 9 | 10 | namespace AspNetCore.Authentication.ApiKey 11 | { 12 | public class ApiKeyInRouteValuesHandler : ApiKeyHandlerBase 13 | { 14 | private const string WwwAuthenticateInParameter = "route_values"; 15 | protected override string GetWwwAuthenticateInParameter() => WwwAuthenticateInParameter; 16 | 17 | #if NET8_0_OR_GREATER 18 | protected ApiKeyInRouteValuesHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) 19 | : base(options, logger, encoder) 20 | { 21 | } 22 | 23 | [Obsolete("ISystemClock is obsolete, use TimeProvider on AuthenticationSchemeOptions instead.")] 24 | #endif 25 | public ApiKeyInRouteValuesHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) 26 | : base(options, logger, encoder, clock) 27 | { 28 | } 29 | 30 | protected override Task ParseApiKeyAsync() 31 | { 32 | if (Request.RouteValues.TryGetValue(Options.KeyName, out var value) && value != null && value.GetType() == typeof(string)) 33 | { 34 | return Task.FromResult(value.ToString() ?? string.Empty); 35 | } 36 | 37 | // No ApiKey query parameter found 38 | return Task.FromResult(string.Empty); 39 | } 40 | } 41 | } 42 | #endif -------------------------------------------------------------------------------- /src/AspNetCore.Authentication.ApiKey/ApiKeyOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using Microsoft.AspNetCore.Authentication; 5 | using System.Diagnostics.CodeAnalysis; 6 | 7 | namespace AspNetCore.Authentication.ApiKey 8 | { 9 | /// 10 | /// Inherited from to allow extra option properties for 'ApiKey' authentication. 11 | /// 12 | public class ApiKeyOptions : AuthenticationSchemeOptions 13 | { 14 | public ApiKeyOptions() 15 | { 16 | Events = new ApiKeyEvents(); 17 | } 18 | 19 | /// 20 | /// This is required property. It is the name of the header or query parameter of the API Key. 21 | /// 22 | public string KeyName { get; set; } = string.Empty; 23 | 24 | /// 25 | /// Gets or sets the realm property. It is used with WWW-Authenticate response header when challenging un-authenticated requests. 26 | /// Required to be set if SuppressWWWAuthenticateHeader is not set to true. 27 | /// 28 | /// 29 | public string? Realm { get; set; } 30 | 31 | /// 32 | /// Default value is false. 33 | /// When set to true, it will NOT return WWW-Authenticate response header when challenging un-authenticated requests. 34 | /// When set to false, it will return WWW-Authenticate response header when challenging un-authenticated requests. 35 | /// It is normally used to disable browser prompt when doing ajax calls. 36 | /// 37 | /// 38 | public bool SuppressWWWAuthenticateHeader { get; set; } 39 | 40 | /// 41 | /// The object provided by the application to process events raised by the api key authentication middleware. 42 | /// The application may implement the interface fully, or it may create an instance of 43 | /// and assign delegates only to the events it wants to process. 44 | /// 45 | public new ApiKeyEvents Events 46 | { 47 | get => (ApiKeyEvents)base.Events!; 48 | set => base.Events = value; 49 | } 50 | 51 | /// 52 | /// Default value is false. 53 | /// If set to true, property returned from method is not compared with the key parsed from the request. 54 | /// This extra check did not existed in the previous version. So you if want to revert back to old version validation, please set this to true. 55 | /// 56 | public bool ForLegacyIgnoreExtraValidatedApiKeyCheck { get; set; } 57 | 58 | /// 59 | /// Default value is false. 60 | /// If set to true then value of property is used as scheme name on the WWW-Authenticate response header when challenging un-authenticated requests. 61 | /// else, the authentication scheme name (set when adding this authentication to the authentication builder) is used as scheme name on the WWW-Authenticate response header when challenging un-authenticated requests. 62 | /// 63 | public bool ForLegacyUseKeyNameAsSchemeNameOnWWWAuthenticateHeader { get; set; } 64 | 65 | #if !(NET461 || NETSTANDARD2_0) 66 | /// 67 | /// Default value is false. 68 | /// If set to true, it checks if AllowAnonymous filter on controller action or metadata on the endpoint which, if found, it does not try to authenticate the request. 69 | /// 70 | public bool IgnoreAuthenticationIfAllowAnonymous { get; set; } 71 | #endif 72 | 73 | #if NET5_0_OR_GREATER 74 | [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] 75 | #endif 76 | internal Type? ApiKeyProviderType { get; set; } = null; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/AspNetCore.Authentication.ApiKey/ApiKeyPostConfigureOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using Microsoft.Extensions.Options; 5 | 6 | namespace AspNetCore.Authentication.ApiKey 7 | { 8 | /// 9 | /// This post configure options checks whether the required option properties are set or not on . 10 | /// 11 | class ApiKeyPostConfigureOptions : IPostConfigureOptions 12 | { 13 | public void PostConfigure(string? name, ApiKeyOptions options) 14 | { 15 | if (!options.SuppressWWWAuthenticateHeader && string.IsNullOrWhiteSpace(options.Realm)) 16 | { 17 | throw new InvalidOperationException($"{nameof(ApiKeyOptions.Realm)} must be set in {typeof(ApiKeyOptions).Name} when setting up the authentication."); 18 | } 19 | 20 | if (string.IsNullOrWhiteSpace(options.KeyName)) 21 | { 22 | throw new InvalidOperationException($"{nameof(ApiKeyOptions.KeyName)} must be set in {typeof(ApiKeyOptions).Name} when setting up the authentication."); 23 | } 24 | 25 | if (options.Events?.OnValidateKey == null && options.EventsType == null && options.ApiKeyProviderType == null) 26 | { 27 | throw new InvalidOperationException($"Either {nameof(ApiKeyOptions.Events.OnValidateKey)} delegate on configure options {nameof(ApiKeyOptions.Events)} should be set or use an extention method with type parameter of type {nameof(IApiKeyProvider)}."); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/AspNetCore.Authentication.ApiKey/ApiKeyUtils.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using System.Security.Claims; 5 | 6 | namespace AspNetCore.Authentication.ApiKey 7 | { 8 | /// 9 | /// Utility class. 10 | /// 11 | internal static class ApiKeyUtils 12 | { 13 | /// 14 | /// Builds Claims Principal from the provided information. 15 | /// If the does not have claim of type then will be added as claim of type . 16 | /// If the does not have claim of type then will be added as claim of type . 17 | /// 18 | /// The owner name. 19 | /// The scheme name. 20 | /// The claims issuer. 21 | /// The list of claims. 22 | /// 23 | internal static ClaimsPrincipal BuildClaimsPrincipal(string? ownerName, string schemeName, string? claimsIssuer, IEnumerable? claims = null) 24 | { 25 | if (string.IsNullOrWhiteSpace(schemeName)) throw new ArgumentNullException(nameof(schemeName)); 26 | 27 | var claimsList = new List(); 28 | if (claims != null) claimsList.AddRange(claims); 29 | 30 | if (!string.IsNullOrWhiteSpace(ownerName)) 31 | { 32 | if (!claimsList.Any(c => c.Type.Equals(ClaimTypes.NameIdentifier, StringComparison.OrdinalIgnoreCase))) 33 | { 34 | claimsList.Add(new Claim(ClaimTypes.NameIdentifier, ownerName, ClaimValueTypes.String, claimsIssuer)); 35 | } 36 | 37 | if (!claimsList.Any(c => c.Type.Equals(ClaimTypes.Name, StringComparison.OrdinalIgnoreCase))) 38 | { 39 | claimsList.Add(new Claim(ClaimTypes.Name, ownerName, ClaimValueTypes.String, claimsIssuer)); 40 | } 41 | } 42 | 43 | return new ClaimsPrincipal(new ClaimsIdentity(claimsList, schemeName)); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/AspNetCore.Authentication.ApiKey/AspNetCore.Authentication.ApiKey.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0;net8.0;net7.0;net6.0;net5.0;netcoreapp3.1;netcoreapp3.0;netstandard2.0;net461 5 | 9.0.0 6 | https://github.com/mihirdilip/aspnetcore-authentication-apiKey/tree/$(Version) 7 | https://github.com/mihirdilip/aspnetcore-authentication-apiKey/tree/$(Version) 8 | aspnetcore, security, authentication, microsoft, microsoft.aspnetcore.authentication, microsoft-aspnetcore-authentication, microsoft.aspnetcore.authentication.apikey, microsoft-aspnetcore-authentication-apikey, asp-net-core, netstandard, netstandard20, apikey-authentication, api-key-authentication, apikeyauthentication, dotnetcore, dotnetcore3.1, net5, net5.0, net6, net6.0, net7, net7.0, net8, net8.0, net9, net9.0, asp-net-core-apikey-authentication, aspnetcore-apikey-authentication, net5-apikey-authentication, asp-net-core-authentication, aspnetcore-authentication, net5-authentication, asp, aspnet, apikey, api-key, authentication-scheme 9 | - net9.0 support added 10 | - Sample project for net9.0 added 11 | - Readme updated 12 | - Nullable reference types enabled 13 | - Language version set to latest 14 | - Implicit usings enabled 15 | - AOT support added 16 | 17 | Easy to use and very light weight Microsoft style API Key Authentication Implementation for ASP.NET Core. It can be setup so that it can accept API Key either in Header, Authorization Header, QueryParams, HeaderOrQueryParams or RouteValues. 18 | Mihir Dilip 19 | Mihir Dilip 20 | Copyright (c) 2025 Mihir Dilip 21 | true 22 | $(AssemblyName) 23 | git 24 | 25 | latest 26 | enable 27 | enable 28 | true 29 | true 30 | LICENSE 31 | README.md 32 | true 33 | true 34 | true 35 | true 36 | 37 | 38 | 39 | false 40 | 41 | 42 | 43 | 44 | true 45 | $(SolutionDir)key.snk 46 | false 47 | 48 | 49 | 50 | 51 | true 52 | true 53 | true 54 | snupkg 55 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | <_Parameter1>$(MSBuildProjectName).Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100ad90edc665c218d12c5d8294e875121644d4c45d37b7cb813aba8d82c560e5bfd5ce8ab3acde2de78c3a2c4ea7e7ecfdd5819820d7c39c280701d629b9da2238a695cc17d51daf616c5c0dcac5d3a2981908a00db4ada980628671782d9776b2d01e9785d5760e169d31f507e178b333bd7b3b197d58fea9795c38774e4380c1 67 | 68 | 69 | 70 | 71 | bin\Release\$(TargetFramework)\AspNetCore.Authentication.ApiKey.xml 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | True 85 | 86 | 87 | 88 | True 89 | \ 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/AspNetCore.Authentication.ApiKey/CompatibilitySuppressions.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | CP0001 6 | T:AspNetCore.Authentication.ApiKey.ApiKeyInRouteValuesHandler 7 | lib/netstandard2.0/AspNetCore.Authentication.ApiKey.dll 8 | lib/netcoreapp3.0/AspNetCore.Authentication.ApiKey.dll 9 | 10 | 11 | CP0002 12 | M:AspNetCore.Authentication.ApiKey.ApiKeyHandlerBase.#ctor(Microsoft.Extensions.Options.IOptionsMonitor{AspNetCore.Authentication.ApiKey.ApiKeyOptions},Microsoft.Extensions.Logging.ILoggerFactory,System.Text.Encodings.Web.UrlEncoder) 13 | lib/net7.0/AspNetCore.Authentication.ApiKey.dll 14 | lib/net8.0/AspNetCore.Authentication.ApiKey.dll 15 | 16 | 17 | CP0002 18 | M:AspNetCore.Authentication.ApiKey.ApiKeyInAuthorizationHeaderHandler.#ctor(Microsoft.Extensions.Options.IOptionsMonitor{AspNetCore.Authentication.ApiKey.ApiKeyOptions},Microsoft.Extensions.Logging.ILoggerFactory,System.Text.Encodings.Web.UrlEncoder) 19 | lib/net7.0/AspNetCore.Authentication.ApiKey.dll 20 | lib/net8.0/AspNetCore.Authentication.ApiKey.dll 21 | 22 | 23 | CP0002 24 | M:AspNetCore.Authentication.ApiKey.ApiKeyInHeaderHandler.#ctor(Microsoft.Extensions.Options.IOptionsMonitor{AspNetCore.Authentication.ApiKey.ApiKeyOptions},Microsoft.Extensions.Logging.ILoggerFactory,System.Text.Encodings.Web.UrlEncoder) 25 | lib/net7.0/AspNetCore.Authentication.ApiKey.dll 26 | lib/net8.0/AspNetCore.Authentication.ApiKey.dll 27 | 28 | 29 | CP0002 30 | M:AspNetCore.Authentication.ApiKey.ApiKeyInHeaderOrQueryParamsHandler.#ctor(Microsoft.Extensions.Options.IOptionsMonitor{AspNetCore.Authentication.ApiKey.ApiKeyOptions},Microsoft.Extensions.Logging.ILoggerFactory,System.Text.Encodings.Web.UrlEncoder) 31 | lib/net7.0/AspNetCore.Authentication.ApiKey.dll 32 | lib/net8.0/AspNetCore.Authentication.ApiKey.dll 33 | 34 | 35 | CP0002 36 | M:AspNetCore.Authentication.ApiKey.ApiKeyInQueryParamsHandler.#ctor(Microsoft.Extensions.Options.IOptionsMonitor{AspNetCore.Authentication.ApiKey.ApiKeyOptions},Microsoft.Extensions.Logging.ILoggerFactory,System.Text.Encodings.Web.UrlEncoder) 37 | lib/net7.0/AspNetCore.Authentication.ApiKey.dll 38 | lib/net8.0/AspNetCore.Authentication.ApiKey.dll 39 | 40 | 41 | CP0002 42 | M:AspNetCore.Authentication.ApiKey.ApiKeyInRouteValuesHandler.#ctor(Microsoft.Extensions.Options.IOptionsMonitor{AspNetCore.Authentication.ApiKey.ApiKeyOptions},Microsoft.Extensions.Logging.ILoggerFactory,System.Text.Encodings.Web.UrlEncoder) 43 | lib/net7.0/AspNetCore.Authentication.ApiKey.dll 44 | lib/net8.0/AspNetCore.Authentication.ApiKey.dll 45 | 46 | 47 | CP0002 48 | M:AspNetCore.Authentication.ApiKey.ApiKeyExtensions.AddApiKeyInRouteValues(Microsoft.AspNetCore.Authentication.AuthenticationBuilder) 49 | lib/netstandard2.0/AspNetCore.Authentication.ApiKey.dll 50 | lib/netcoreapp3.0/AspNetCore.Authentication.ApiKey.dll 51 | 52 | 53 | CP0002 54 | M:AspNetCore.Authentication.ApiKey.ApiKeyExtensions.AddApiKeyInRouteValues(Microsoft.AspNetCore.Authentication.AuthenticationBuilder,System.Action{AspNetCore.Authentication.ApiKey.ApiKeyOptions}) 55 | lib/netstandard2.0/AspNetCore.Authentication.ApiKey.dll 56 | lib/netcoreapp3.0/AspNetCore.Authentication.ApiKey.dll 57 | 58 | 59 | CP0002 60 | M:AspNetCore.Authentication.ApiKey.ApiKeyExtensions.AddApiKeyInRouteValues(Microsoft.AspNetCore.Authentication.AuthenticationBuilder,System.String) 61 | lib/netstandard2.0/AspNetCore.Authentication.ApiKey.dll 62 | lib/netcoreapp3.0/AspNetCore.Authentication.ApiKey.dll 63 | 64 | 65 | CP0002 66 | M:AspNetCore.Authentication.ApiKey.ApiKeyExtensions.AddApiKeyInRouteValues(Microsoft.AspNetCore.Authentication.AuthenticationBuilder,System.String,System.Action{AspNetCore.Authentication.ApiKey.ApiKeyOptions}) 67 | lib/netstandard2.0/AspNetCore.Authentication.ApiKey.dll 68 | lib/netcoreapp3.0/AspNetCore.Authentication.ApiKey.dll 69 | 70 | 71 | CP0002 72 | M:AspNetCore.Authentication.ApiKey.ApiKeyExtensions.AddApiKeyInRouteValues(Microsoft.AspNetCore.Authentication.AuthenticationBuilder,System.String,System.String,System.Action{AspNetCore.Authentication.ApiKey.ApiKeyOptions}) 73 | lib/netstandard2.0/AspNetCore.Authentication.ApiKey.dll 74 | lib/netcoreapp3.0/AspNetCore.Authentication.ApiKey.dll 75 | 76 | 77 | CP0002 78 | M:AspNetCore.Authentication.ApiKey.ApiKeyExtensions.AddApiKeyInRouteValues``1(Microsoft.AspNetCore.Authentication.AuthenticationBuilder) 79 | lib/netstandard2.0/AspNetCore.Authentication.ApiKey.dll 80 | lib/netcoreapp3.0/AspNetCore.Authentication.ApiKey.dll 81 | 82 | 83 | CP0002 84 | M:AspNetCore.Authentication.ApiKey.ApiKeyExtensions.AddApiKeyInRouteValues``1(Microsoft.AspNetCore.Authentication.AuthenticationBuilder,System.Action{AspNetCore.Authentication.ApiKey.ApiKeyOptions}) 85 | lib/netstandard2.0/AspNetCore.Authentication.ApiKey.dll 86 | lib/netcoreapp3.0/AspNetCore.Authentication.ApiKey.dll 87 | 88 | 89 | CP0002 90 | M:AspNetCore.Authentication.ApiKey.ApiKeyExtensions.AddApiKeyInRouteValues``1(Microsoft.AspNetCore.Authentication.AuthenticationBuilder,System.String) 91 | lib/netstandard2.0/AspNetCore.Authentication.ApiKey.dll 92 | lib/netcoreapp3.0/AspNetCore.Authentication.ApiKey.dll 93 | 94 | 95 | CP0002 96 | M:AspNetCore.Authentication.ApiKey.ApiKeyExtensions.AddApiKeyInRouteValues``1(Microsoft.AspNetCore.Authentication.AuthenticationBuilder,System.String,System.Action{AspNetCore.Authentication.ApiKey.ApiKeyOptions}) 97 | lib/netstandard2.0/AspNetCore.Authentication.ApiKey.dll 98 | lib/netcoreapp3.0/AspNetCore.Authentication.ApiKey.dll 99 | 100 | 101 | CP0002 102 | M:AspNetCore.Authentication.ApiKey.ApiKeyExtensions.AddApiKeyInRouteValues``1(Microsoft.AspNetCore.Authentication.AuthenticationBuilder,System.String,System.String,System.Action{AspNetCore.Authentication.ApiKey.ApiKeyOptions}) 103 | lib/netstandard2.0/AspNetCore.Authentication.ApiKey.dll 104 | lib/netcoreapp3.0/AspNetCore.Authentication.ApiKey.dll 105 | 106 | 107 | CP0002 108 | M:AspNetCore.Authentication.ApiKey.ApiKeyOptions.get_IgnoreAuthenticationIfAllowAnonymous 109 | lib/netstandard2.0/AspNetCore.Authentication.ApiKey.dll 110 | lib/netcoreapp3.0/AspNetCore.Authentication.ApiKey.dll 111 | 112 | 113 | CP0002 114 | M:AspNetCore.Authentication.ApiKey.ApiKeyOptions.set_IgnoreAuthenticationIfAllowAnonymous(System.Boolean) 115 | lib/netstandard2.0/AspNetCore.Authentication.ApiKey.dll 116 | lib/netcoreapp3.0/AspNetCore.Authentication.ApiKey.dll 117 | 118 | -------------------------------------------------------------------------------- /src/AspNetCore.Authentication.ApiKey/Events/ApiKeyAuthenticationFailedContext.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using Microsoft.AspNetCore.Authentication; 5 | using Microsoft.AspNetCore.Http; 6 | 7 | namespace AspNetCore.Authentication.ApiKey 8 | { 9 | /// 10 | /// Context used when authentication is failed. 11 | /// 12 | public class ApiKeyAuthenticationFailedContext : ResultContext 13 | { 14 | /// 15 | /// Constructor. 16 | /// 17 | /// 18 | /// 19 | /// 20 | /// 21 | public ApiKeyAuthenticationFailedContext(HttpContext context, AuthenticationScheme scheme, ApiKeyOptions options, Exception exception) 22 | : base(context, scheme, options) 23 | { 24 | Exception = exception; 25 | } 26 | 27 | /// 28 | /// The Exception thrown when authenticating. 29 | /// 30 | public Exception Exception { get; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/AspNetCore.Authentication.ApiKey/Events/ApiKeyAuthenticationSucceededContext.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using Microsoft.AspNetCore.Authentication; 5 | using Microsoft.AspNetCore.Http; 6 | using System.Security.Claims; 7 | 8 | namespace AspNetCore.Authentication.ApiKey 9 | { 10 | /// 11 | /// Context used when authentication is succeeded. 12 | /// 13 | public class ApiKeyAuthenticationSucceededContext : ResultContext 14 | { 15 | /// 16 | /// Constructor. 17 | /// 18 | /// 19 | /// 20 | /// 21 | /// 22 | public ApiKeyAuthenticationSucceededContext(HttpContext context, AuthenticationScheme scheme, ApiKeyOptions options, ClaimsPrincipal principal) 23 | : base(context, scheme, options) 24 | { 25 | Principal = principal; 26 | } 27 | 28 | /// 29 | /// Get the containing the user claims. 30 | /// 31 | public new ClaimsPrincipal? Principal { get => base.Principal; private set => base.Principal = value; } 32 | 33 | /// 34 | /// Called to replace the claims principal. The supplied principal will replace the value of the 35 | /// Principal property, which determines the identity of the authenticated request. 36 | /// 37 | /// The to be used as the replacement. 38 | /// 39 | public void ReplacePrincipal(ClaimsPrincipal principal) 40 | { 41 | Principal = principal ?? throw new ArgumentNullException(nameof(principal)); 42 | } 43 | 44 | /// 45 | /// Called to reject the incoming principal. This may be done if the application has determined the 46 | /// account is no longer active, and the request should be treated as if it was anonymous. 47 | /// 48 | public void RejectPrincipal() => base.Principal = null; 49 | 50 | /// 51 | /// Adds a claim to the current authenticated identity. 52 | /// 53 | /// 54 | /// 55 | public void AddClaim(Claim claim) 56 | { 57 | if (claim == null) throw new ArgumentNullException(nameof(claim)); 58 | (Principal?.Identity as ClaimsIdentity)?.AddClaim(claim); 59 | } 60 | 61 | /// 62 | /// Adds a list of claims to the current authenticated identity. 63 | /// 64 | /// 65 | /// 66 | public void AddClaims(IEnumerable claims) 67 | { 68 | if (claims == null) throw new ArgumentNullException(nameof(claims)); 69 | (Principal?.Identity as ClaimsIdentity)?.AddClaims(claims); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/AspNetCore.Authentication.ApiKey/Events/ApiKeyEvents.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | namespace AspNetCore.Authentication.ApiKey 5 | { 6 | /// 7 | /// ApiKey Events. 8 | /// 9 | public class ApiKeyEvents 10 | { 11 | /// 12 | /// A delegate assigned to this property will be invoked just before validating api key. 13 | /// 14 | /// 15 | /// You must provide a delegate for this property for authentication to occur. 16 | /// In your delegate you should either call context.ValidationSucceeded() which will handle construction of authentication principal which will be assiged the context.Principal property and call context.Success(), 17 | /// or construct an authentication principal & attach it to the context.Principal property and finally call context.Success() method. 18 | /// If only context.Principal property set without calling context.Success() method then, Success() method is automaticalled called. 19 | /// 20 | public Func? OnValidateKey { get; set; } 21 | 22 | /// 23 | /// A delegate assigned to this property will be invoked when the authentication succeeds. It will not be called if delegate is assigned. 24 | /// It can be used for adding claims, headers, etc to the response. 25 | /// 26 | /// 27 | /// Only use this if you know what you are doing. 28 | /// 29 | public Func? OnAuthenticationSucceeded { get; set; } 30 | 31 | /// 32 | /// A delegate assigned to this property will be invoked when the authentication fails. 33 | /// 34 | public Func? OnAuthenticationFailed { get; set; } 35 | 36 | /// 37 | /// A delegate assigned to this property will be invoked before a challenge is sent back to the caller when handling unauthorized response. 38 | /// 39 | /// 40 | /// Only use this if you know what you are doing and if you want to use custom implementation. 41 | /// Set the delegate to deal with 401 challenge concerns, if an authentication scheme in question 42 | /// deals an authentication interaction as part of it's request flow. (like adding a response header, or 43 | /// changing the 401 result to 302 of a login page or external sign-in location.) 44 | /// Call context.Handled() at the end so that any default logic for this challenge will be skipped. 45 | /// 46 | public Func? OnHandleChallenge { get; set; } 47 | 48 | /// 49 | /// A delegate assigned to this property will be invoked if Authorization fails and results in a Forbidden response. 50 | /// 51 | /// 52 | /// Only use this if you know what you are doing and if you want to use custom implementation. 53 | /// Set the delegate to handle Forbid. 54 | /// Call context.Handled() at the end so that any default logic will be skipped. 55 | /// 56 | public Func? OnHandleForbidden { get; set; } 57 | 58 | 59 | 60 | 61 | 62 | /// 63 | /// Invoked when validating api key. 64 | /// 65 | /// 66 | /// A Task. 67 | public virtual Task ValidateKeyAsync(ApiKeyValidateKeyContext context) => OnValidateKey == null ? Task.CompletedTask : OnValidateKey(context); 68 | 69 | /// 70 | /// Invoked when the authentication succeeds. 71 | /// 72 | /// 73 | /// A Task. 74 | public virtual Task AuthenticationSucceededAsync(ApiKeyAuthenticationSucceededContext context) => OnAuthenticationSucceeded == null ? Task.CompletedTask : OnAuthenticationSucceeded(context); 75 | 76 | /// 77 | /// Invoked when the authentication fails. 78 | /// 79 | /// 80 | /// A Task. 81 | public virtual Task AuthenticationFailedAsync(ApiKeyAuthenticationFailedContext context) => OnAuthenticationFailed == null ? Task.CompletedTask : OnAuthenticationFailed(context); 82 | 83 | /// 84 | /// Invoked before a challenge is sent back to the caller when handling unauthorized response. 85 | /// 86 | /// 87 | /// Override this method to deal with 401 challenge concerns, if an authentication scheme in question 88 | /// deals an authentication interaction as part of it's request flow. (like adding a response header, or 89 | /// changing the 401 result to 302 of a login page or external sign-in location.) 90 | /// Call context.Handled() at the end so that any default logic for this challenge will be skipped. 91 | /// 92 | /// 93 | /// A Task. 94 | public virtual Task HandleChallengeAsync(ApiKeyHandleChallengeContext context) => OnHandleChallenge == null ? Task.CompletedTask : OnHandleChallenge(context); 95 | 96 | /// 97 | /// Invoked if Authorization fails and results in a Forbidden response. 98 | /// 99 | /// 100 | /// Override this method to handle Forbid. 101 | /// Call context.Handled() at the end so that any default logic will be skipped. 102 | /// 103 | /// 104 | /// A Task. 105 | public virtual Task HandleForbiddenAsync(ApiKeyHandleForbiddenContext context) => OnHandleForbidden == null ? Task.CompletedTask : OnHandleForbidden(context); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/AspNetCore.Authentication.ApiKey/Events/ApiKeyHandleChallengeContext.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using Microsoft.AspNetCore.Authentication; 5 | using Microsoft.AspNetCore.Http; 6 | 7 | namespace AspNetCore.Authentication.ApiKey 8 | { 9 | /// 10 | /// Context used when challenging unauthorized response. 11 | /// 12 | public class ApiKeyHandleChallengeContext : PropertiesContext 13 | { 14 | /// 15 | /// Constructor. 16 | /// 17 | /// 18 | /// 19 | /// 20 | /// 21 | public ApiKeyHandleChallengeContext(HttpContext context, AuthenticationScheme scheme, ApiKeyOptions options, AuthenticationProperties properties) 22 | : base(context, scheme, options, properties) 23 | { 24 | } 25 | 26 | /// 27 | /// Gets IsHandled property. 28 | /// True means response is handled and any default logic for this challenge will be skipped. 29 | /// 30 | public bool IsHandled { get; private set; } 31 | 32 | /// 33 | /// Marks as response handled and any default logic for this challenge will be skipped. 34 | /// 35 | public void Handled() => IsHandled = true; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/AspNetCore.Authentication.ApiKey/Events/ApiKeyHandleForbiddenContext.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using Microsoft.AspNetCore.Authentication; 5 | using Microsoft.AspNetCore.Http; 6 | 7 | namespace AspNetCore.Authentication.ApiKey 8 | { 9 | /// 10 | /// Context used when handling forbidden response. 11 | /// 12 | public class ApiKeyHandleForbiddenContext : PropertiesContext 13 | { 14 | /// 15 | /// Constructor. 16 | /// 17 | /// 18 | /// 19 | /// 20 | /// 21 | public ApiKeyHandleForbiddenContext(HttpContext context, AuthenticationScheme scheme, ApiKeyOptions options, AuthenticationProperties properties) 22 | : base(context, scheme, options, properties) 23 | { 24 | } 25 | 26 | /// 27 | /// Gets IsHandled property. 28 | /// True means response is handled and any default logic will be skipped. 29 | /// 30 | public bool IsHandled { get; private set; } 31 | 32 | /// 33 | /// Marks as response handled and any default logic will be skipped. 34 | /// 35 | public void Handled() => IsHandled = true; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/AspNetCore.Authentication.ApiKey/Events/ApiKeyValidateKeyContext.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using Microsoft.AspNetCore.Authentication; 5 | using Microsoft.AspNetCore.Http; 6 | using System.Security.Claims; 7 | 8 | namespace AspNetCore.Authentication.ApiKey 9 | { 10 | /// 11 | /// Context used for validating key. 12 | /// 13 | public class ApiKeyValidateKeyContext : ResultContext 14 | { 15 | /// 16 | /// Constructor. 17 | /// 18 | /// 19 | /// 20 | /// 21 | /// 22 | public ApiKeyValidateKeyContext(HttpContext context, AuthenticationScheme scheme, ApiKeyOptions options, string apiKey) 23 | : base(context, scheme, options) 24 | { 25 | ApiKey = apiKey; 26 | } 27 | 28 | /// 29 | /// Gets the Api Key. 30 | /// 31 | public string ApiKey { get; } 32 | 33 | /// 34 | /// Calling this method will handle construction of authentication principal () 35 | /// which will be assiged to the property 36 | /// and method will also be called. 37 | /// 38 | /// Claims to be added to the identity. 39 | public void ValidationSucceeded(IEnumerable? claims = null) 40 | { 41 | ValidationSucceeded(null, claims); 42 | } 43 | 44 | /// 45 | /// Calling this method will handle construction of authentication principal () 46 | /// which will be assiged to the property 47 | /// and method will also be called. 48 | /// 49 | /// The owner name to be added to claims as and if not already added with . 50 | /// Claims to be added to the identity. 51 | public void ValidationSucceeded(string? ownerName, IEnumerable? claims = null) 52 | { 53 | Principal = ApiKeyUtils.BuildClaimsPrincipal(ownerName, Scheme.Name, Options.ClaimsIssuer, claims); 54 | Success(); 55 | } 56 | 57 | /// 58 | /// If parameter passed is empty or null then NoResult() method is called 59 | /// otherwise, method will be called. 60 | /// 61 | /// (Optional) The failure message. 62 | public void ValidationFailed(string? failureMessage = null) 63 | { 64 | if (string.IsNullOrWhiteSpace(failureMessage)) 65 | { 66 | NoResult(); 67 | return; 68 | } 69 | Fail(failureMessage); 70 | } 71 | 72 | /// 73 | /// Calling this method is same as calling method. 74 | /// 75 | /// The failure exception. 76 | public void ValidationFailed(Exception failureException) 77 | { 78 | Fail(failureException); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/AspNetCore.Authentication.ApiKey/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Maintainability", "CA1510:Use ArgumentNullException throw helper", Justification = "", Scope = "member", Target = "~M:AspNetCore.Authentication.ApiKey.ApiKeyAuthenticationSucceededContext.AddClaim(System.Security.Claims.Claim)")] 9 | [assembly: SuppressMessage("Maintainability", "CA1510:Use ArgumentNullException throw helper", Justification = "", Scope = "member", Target = "~M:AspNetCore.Authentication.ApiKey.ApiKeyAuthenticationSucceededContext.AddClaims(System.Collections.Generic.IEnumerable{System.Security.Claims.Claim})")] 10 | [assembly: SuppressMessage("Style", "IDE0130:Namespace does not match folder structure", Justification = "", Scope = "namespace", Target = "~N:AspNetCore.Authentication.ApiKey")] 11 | [assembly: SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "")] 12 | -------------------------------------------------------------------------------- /src/AspNetCore.Authentication.ApiKey/IApiKey.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using System.Security.Claims; 5 | 6 | namespace AspNetCore.Authentication.ApiKey 7 | { 8 | /// 9 | /// API Key Details 10 | /// 11 | public interface IApiKey 12 | { 13 | /// 14 | /// API Key 15 | /// 16 | string Key { get; } 17 | 18 | /// 19 | /// Owner of the API Key. It can be username or any other key owner name. 20 | /// 21 | string OwnerName { get; } 22 | 23 | /// 24 | /// Optional list of claims to be sent back with the authentication request. 25 | /// 26 | IReadOnlyCollection Claims { get; } 27 | } 28 | } -------------------------------------------------------------------------------- /src/AspNetCore.Authentication.ApiKey/IApiKeyProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | namespace AspNetCore.Authentication.ApiKey 5 | { 6 | /// 7 | /// Implementation of this interface will be used by the 'ApiKey' authentication handler to validated and get details from the key. 8 | /// 9 | public interface IApiKeyProvider 10 | { 11 | /// 12 | /// Validates the key and provides with and instance of . 13 | /// 14 | /// 15 | /// 16 | Task ProvideAsync(string key); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihirdilip/aspnetcore-authentication-apikey/3635a6a810edafa9516b63067d7529de37502a4c/src/key.snk -------------------------------------------------------------------------------- /test/AspNetCore.Authentication.ApiKey.Tests/ApiKeyDefaultsTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using Xunit; 5 | 6 | namespace AspNetCore.Authentication.ApiKey.Tests 7 | { 8 | public class ApiKeyDefaultsTests 9 | { 10 | [Fact] 11 | public void AuthenticationSchemeValueTest() 12 | { 13 | Assert.Equal("ApiKey", ApiKeyDefaults.AuthenticationScheme); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/AspNetCore.Authentication.ApiKey.Tests/ApiKeyInHeaderHandlerTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using AspNetCore.Authentication.ApiKey.Tests.Infrastructure; 5 | using Microsoft.AspNetCore.Authentication; 6 | using Microsoft.AspNetCore.TestHost; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Options; 9 | using System.Net; 10 | using System.Net.Http; 11 | using Xunit; 12 | 13 | namespace AspNetCore.Authentication.ApiKey.Tests 14 | { 15 | public class ApiKeyInHeaderHandlerTests : IDisposable 16 | { 17 | private readonly TestServer _server; 18 | private readonly HttpClient _client; 19 | private readonly TestServer _serverWithProvider; 20 | private readonly HttpClient _clientWithProvider; 21 | private bool _disposedValue; 22 | 23 | public ApiKeyInHeaderHandlerTests() 24 | { 25 | _server = TestServerBuilder.BuildInHeaderServer(); 26 | _client = _server.CreateClient(); 27 | 28 | _serverWithProvider = TestServerBuilder.BuildInHeaderServerWithProvider(); 29 | _clientWithProvider = _serverWithProvider.CreateClient(); 30 | } 31 | 32 | [Fact] 33 | public async Task Verify_Handler() 34 | { 35 | var services = _server.Host.Services; 36 | var schemeProvider = services.GetRequiredService(); 37 | Assert.NotNull(schemeProvider); 38 | 39 | var scheme = await schemeProvider.GetDefaultAuthenticateSchemeAsync(); 40 | Assert.NotNull(scheme); 41 | Assert.Equal(typeof(ApiKeyInHeaderHandler), scheme.HandlerType); 42 | 43 | var apiKeyOptionsSnapshot = services.GetService>(); 44 | var apiKeyOptions = apiKeyOptionsSnapshot?.Get(scheme.Name); 45 | Assert.NotNull(apiKeyOptions); 46 | Assert.NotNull(apiKeyOptions.Events?.OnValidateKey); 47 | Assert.Null(apiKeyOptions.ApiKeyProviderType); 48 | 49 | var apiKeyProvider = services.GetService(); 50 | Assert.Null(apiKeyProvider); 51 | } 52 | 53 | [Fact] 54 | public async Task TApiKeyProvider_Verify_Handler() 55 | { 56 | var services = _serverWithProvider.Host.Services; 57 | var schemeProvider = services.GetRequiredService(); 58 | Assert.NotNull(schemeProvider); 59 | 60 | var scheme = await schemeProvider.GetDefaultAuthenticateSchemeAsync(); 61 | Assert.NotNull(scheme); 62 | Assert.Equal(typeof(ApiKeyInHeaderHandler), scheme.HandlerType); 63 | 64 | var apiKeyOptionsSnapshot = services.GetService>(); 65 | var apiKeyOptions = apiKeyOptionsSnapshot?.Get(scheme.Name); 66 | Assert.NotNull(apiKeyOptions); 67 | Assert.Null(apiKeyOptions.Events?.OnValidateKey); 68 | Assert.NotNull(apiKeyOptions.ApiKeyProviderType); 69 | Assert.Equal(typeof(FakeApiKeyProvider), apiKeyOptions.ApiKeyProviderType); 70 | 71 | var apiKeyProvider = services.GetService(); 72 | Assert.NotNull(apiKeyProvider); 73 | Assert.Equal(typeof(FakeApiKeyProvider), apiKeyProvider.GetType()); 74 | } 75 | 76 | [Fact] 77 | public async Task Verify_challenge_www_authenticate_header() 78 | { 79 | using var response = await _client.GetAsync(TestServerBuilder.BaseUrl); 80 | Assert.False(response.IsSuccessStatusCode); 81 | 82 | var wwwAuthenticateHeader = response.Headers.WwwAuthenticate; 83 | Assert.NotEmpty(wwwAuthenticateHeader); 84 | 85 | var wwwAuthenticateHeaderToMatch = Assert.Single(wwwAuthenticateHeader); 86 | Assert.NotNull(wwwAuthenticateHeaderToMatch); 87 | Assert.Equal(ApiKeyDefaults.AuthenticationScheme, wwwAuthenticateHeaderToMatch.Scheme); 88 | Assert.Equal($"realm=\"{TestServerBuilder.Realm}\", charset=\"UTF-8\", in=\"header\", key_name=\"{FakeApiKeys.KeyName}\"", wwwAuthenticateHeaderToMatch.Parameter); 89 | } 90 | 91 | [Fact] 92 | public async Task Unauthorized() 93 | { 94 | using var request = new HttpRequestMessage(HttpMethod.Get, TestServerBuilder.BaseUrl); 95 | using var response = await _client.SendAsync(request); 96 | Assert.False(response.IsSuccessStatusCode); 97 | Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); 98 | } 99 | 100 | [Fact] 101 | public async Task Success() 102 | { 103 | using var request = new HttpRequestMessage(HttpMethod.Get, TestServerBuilder.BaseUrl); 104 | request.Headers.Add(FakeApiKeys.KeyName, FakeApiKeys.FakeKey); 105 | using var response = await _client.SendAsync(request); 106 | Assert.True(response.IsSuccessStatusCode); 107 | Assert.Equal(HttpStatusCode.OK, response.StatusCode); 108 | } 109 | 110 | [Fact] 111 | public async Task Invalid_key_unauthorized() 112 | { 113 | using var request = new HttpRequestMessage(HttpMethod.Get, TestServerBuilder.BaseUrl); 114 | request.Headers.Add(FakeApiKeys.KeyName, FakeApiKeys.FakeInvalidKey); 115 | using var response = await _client.SendAsync(request); 116 | Assert.False(response.IsSuccessStatusCode); 117 | Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); 118 | } 119 | 120 | 121 | [Fact] 122 | public async Task TApiKeyProvider_Unauthorized() 123 | { 124 | using var request = new HttpRequestMessage(HttpMethod.Get, TestServerBuilder.BaseUrl); 125 | using var response = await _clientWithProvider.SendAsync(request); 126 | Assert.False(response.IsSuccessStatusCode); 127 | Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); 128 | } 129 | 130 | [Fact] 131 | public async Task TApiKeyProvider_success() 132 | { 133 | using var request = new HttpRequestMessage(HttpMethod.Get, TestServerBuilder.BaseUrl); 134 | request.Headers.Add(FakeApiKeys.KeyName, FakeApiKeys.FakeKey); 135 | using var response = await _clientWithProvider.SendAsync(request); 136 | Assert.True(response.IsSuccessStatusCode); 137 | Assert.Equal(HttpStatusCode.OK, response.StatusCode); 138 | } 139 | 140 | [Fact] 141 | public async Task TApiKeyProvider_invalid_key_unauthotized() 142 | { 143 | using var request = new HttpRequestMessage(HttpMethod.Get, TestServerBuilder.BaseUrl); 144 | request.Headers.Add(FakeApiKeys.KeyName, FakeApiKeys.FakeInvalidKey); 145 | using var response = await _clientWithProvider.SendAsync(request); 146 | Assert.False(response.IsSuccessStatusCode); 147 | Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); 148 | } 149 | 150 | protected virtual void Dispose(bool disposing) 151 | { 152 | if (!_disposedValue) 153 | { 154 | if (disposing) 155 | { 156 | // TODO: dispose managed state (managed objects) 157 | 158 | _client?.Dispose(); 159 | _server?.Dispose(); 160 | 161 | _clientWithProvider?.Dispose(); 162 | _serverWithProvider?.Dispose(); 163 | } 164 | 165 | // TODO: free unmanaged resources (unmanaged objects) and override finalizer 166 | // TODO: set large fields to null 167 | _disposedValue = true; 168 | } 169 | } 170 | 171 | // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources 172 | // ~ApiKeyInHeaderHandlerTests() 173 | // { 174 | // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 175 | // Dispose(disposing: false); 176 | // } 177 | 178 | public void Dispose() 179 | { 180 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 181 | Dispose(disposing: true); 182 | GC.SuppressFinalize(this); 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /test/AspNetCore.Authentication.ApiKey.Tests/ApiKeyInQueryParamsHandlerTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using AspNetCore.Authentication.ApiKey.Tests.Infrastructure; 5 | using Microsoft.AspNetCore.Authentication; 6 | using Microsoft.AspNetCore.TestHost; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Options; 9 | using System.Net; 10 | using System.Net.Http; 11 | using Xunit; 12 | 13 | namespace AspNetCore.Authentication.ApiKey.Tests 14 | { 15 | public class ApiKeyInQueryParamsHandlerTests : IDisposable 16 | { 17 | private readonly TestServer _server; 18 | private readonly HttpClient _client; 19 | private readonly TestServer _serverWithProvider; 20 | private readonly HttpClient _clientWithProvider; 21 | private bool _disposedValue; 22 | 23 | public ApiKeyInQueryParamsHandlerTests() 24 | { 25 | _server = TestServerBuilder.BuildInQueryParamsServer(); 26 | _client = _server.CreateClient(); 27 | 28 | _serverWithProvider = TestServerBuilder.BuildInQueryParamsServerWithProvider(); 29 | _clientWithProvider = _serverWithProvider.CreateClient(); 30 | } 31 | 32 | [Fact] 33 | public async Task Verify_Handler() 34 | { 35 | var services = _server.Host.Services; 36 | var schemeProvider = services.GetRequiredService(); 37 | Assert.NotNull(schemeProvider); 38 | 39 | var scheme = await schemeProvider.GetDefaultAuthenticateSchemeAsync(); 40 | Assert.NotNull(scheme); 41 | Assert.Equal(typeof(ApiKeyInQueryParamsHandler), scheme.HandlerType); 42 | 43 | var apiKeyOptionsSnapshot = services.GetService>(); 44 | var apiKeyOptions = apiKeyOptionsSnapshot?.Get(scheme.Name); 45 | Assert.NotNull(apiKeyOptions); 46 | Assert.NotNull(apiKeyOptions.Events?.OnValidateKey); 47 | Assert.Null(apiKeyOptions.ApiKeyProviderType); 48 | 49 | var apiKeyProvider = services.GetService(); 50 | Assert.Null(apiKeyProvider); 51 | } 52 | 53 | [Fact] 54 | public async Task TApiKeyProvider_Verify_Handler() 55 | { 56 | var services = _serverWithProvider.Host.Services; 57 | var schemeProvider = services.GetRequiredService(); 58 | Assert.NotNull(schemeProvider); 59 | 60 | var scheme = await schemeProvider.GetDefaultAuthenticateSchemeAsync(); 61 | Assert.NotNull(scheme); 62 | Assert.Equal(typeof(ApiKeyInQueryParamsHandler), scheme.HandlerType); 63 | 64 | var apiKeyOptionsSnapshot = services.GetService>(); 65 | var apiKeyOptions = apiKeyOptionsSnapshot?.Get(scheme.Name); 66 | Assert.NotNull(apiKeyOptions); 67 | Assert.Null(apiKeyOptions.Events?.OnValidateKey); 68 | Assert.NotNull(apiKeyOptions.ApiKeyProviderType); 69 | Assert.Equal(typeof(FakeApiKeyProvider), apiKeyOptions.ApiKeyProviderType); 70 | 71 | var apiKeyProvider = services.GetService(); 72 | Assert.NotNull(apiKeyProvider); 73 | Assert.Equal(typeof(FakeApiKeyProvider), apiKeyProvider.GetType()); 74 | } 75 | 76 | [Fact] 77 | public async Task Verify_challenge_www_authenticate_header() 78 | { 79 | using var response = await _client.GetAsync(TestServerBuilder.BaseUrl); 80 | Assert.False(response.IsSuccessStatusCode); 81 | 82 | var wwwAuthenticateHeader = response.Headers.WwwAuthenticate; 83 | Assert.NotEmpty(wwwAuthenticateHeader); 84 | 85 | var wwwAuthenticateHeaderToMatch = Assert.Single(wwwAuthenticateHeader); 86 | Assert.NotNull(wwwAuthenticateHeaderToMatch); 87 | Assert.Equal(ApiKeyDefaults.AuthenticationScheme, wwwAuthenticateHeaderToMatch.Scheme); 88 | Assert.Equal($"realm=\"{TestServerBuilder.Realm}\", charset=\"UTF-8\", in=\"query_params\", key_name=\"{FakeApiKeys.KeyName}\"", wwwAuthenticateHeaderToMatch.Parameter); 89 | } 90 | 91 | [Fact] 92 | public async Task Unauthorized() 93 | { 94 | using var request = new HttpRequestMessage(HttpMethod.Get, TestServerBuilder.BaseUrl); 95 | using var response = await _client.SendAsync(request); 96 | Assert.False(response.IsSuccessStatusCode); 97 | Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); 98 | } 99 | 100 | [Fact] 101 | public async Task Success() 102 | { 103 | var uri = $"{TestServerBuilder.BaseUrl}?{FakeApiKeys.KeyName}={FakeApiKeys.FakeKey}"; 104 | using var request = new HttpRequestMessage(HttpMethod.Get, uri); 105 | using var response = await _client.SendAsync(request); 106 | Assert.True(response.IsSuccessStatusCode); 107 | Assert.Equal(HttpStatusCode.OK, response.StatusCode); 108 | } 109 | 110 | [Fact] 111 | public async Task Invalid_key_unauthorized() 112 | { 113 | var uri = $"{TestServerBuilder.BaseUrl}?{FakeApiKeys.KeyName}={FakeApiKeys.FakeInvalidKey}"; 114 | using var request = new HttpRequestMessage(HttpMethod.Get, uri); 115 | using var response = await _client.SendAsync(request); 116 | Assert.False(response.IsSuccessStatusCode); 117 | Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); 118 | } 119 | 120 | 121 | 122 | [Fact] 123 | public async Task TApiKeyProvider_Unauthorized() 124 | { 125 | using var request = new HttpRequestMessage(HttpMethod.Get, TestServerBuilder.BaseUrl); 126 | using var response = await _clientWithProvider.SendAsync(request); 127 | Assert.False(response.IsSuccessStatusCode); 128 | Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); 129 | } 130 | 131 | [Fact] 132 | public async Task TApiKeyProvider_success() 133 | { 134 | var uri = $"{TestServerBuilder.BaseUrl}?{FakeApiKeys.KeyName}={FakeApiKeys.FakeKey}"; 135 | using var request = new HttpRequestMessage(HttpMethod.Get, uri); 136 | using var response = await _clientWithProvider.SendAsync(request); 137 | Assert.True(response.IsSuccessStatusCode); 138 | Assert.Equal(HttpStatusCode.OK, response.StatusCode); 139 | } 140 | 141 | [Fact] 142 | public async Task TApiKeyProvider_invalid_key_unauthotized() 143 | { 144 | var uri = $"{TestServerBuilder.BaseUrl}?{FakeApiKeys.KeyName}={FakeApiKeys.FakeInvalidKey}"; 145 | using var request = new HttpRequestMessage(HttpMethod.Get, uri); 146 | using var response = await _clientWithProvider.SendAsync(request); 147 | Assert.False(response.IsSuccessStatusCode); 148 | Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); 149 | } 150 | 151 | protected virtual void Dispose(bool disposing) 152 | { 153 | if (!_disposedValue) 154 | { 155 | if (disposing) 156 | { 157 | // dispose managed state (managed objects) 158 | 159 | _client?.Dispose(); 160 | _server?.Dispose(); 161 | 162 | _clientWithProvider?.Dispose(); 163 | _serverWithProvider?.Dispose(); 164 | } 165 | 166 | // free unmanaged resources (unmanaged objects) and override finalizer 167 | // set large fields to null 168 | _disposedValue = true; 169 | } 170 | } 171 | 172 | // // override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources 173 | // ~ApiKeyInQueryParamsHandlerTests() 174 | // { 175 | // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 176 | // Dispose(disposing: false); 177 | // } 178 | 179 | public void Dispose() 180 | { 181 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 182 | Dispose(disposing: true); 183 | GC.SuppressFinalize(this); 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /test/AspNetCore.Authentication.ApiKey.Tests/ApiKeyInRouteValuesHandlerTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | #if NETCOREAPP3_0_OR_GREATER 4 | using AspNetCore.Authentication.ApiKey.Tests.Infrastructure; 5 | using Microsoft.AspNetCore.Authentication; 6 | using Microsoft.AspNetCore.TestHost; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Options; 9 | using System; 10 | using System.Net; 11 | using System.Net.Http; 12 | using System.Threading.Tasks; 13 | using Xunit; 14 | 15 | namespace AspNetCore.Authentication.ApiKey.Tests 16 | { 17 | public class ApiKeyInRouteValuesHandlerTests : IDisposable 18 | { 19 | private readonly TestServer _server; 20 | private readonly HttpClient _client; 21 | private readonly TestServer _serverWithProvider; 22 | private readonly HttpClient _clientWithProvider; 23 | private bool _disposedValue; 24 | 25 | public ApiKeyInRouteValuesHandlerTests() 26 | { 27 | _server = TestServerBuilder.BuildInRouteValuesServer(); 28 | _client = _server.CreateClient(); 29 | 30 | _serverWithProvider = TestServerBuilder.BuildInRouteValuesServerWithProvider(); 31 | _clientWithProvider = _serverWithProvider.CreateClient(); 32 | } 33 | 34 | [Fact] 35 | public async Task Verify_Handler() 36 | { 37 | var services = _server.Host.Services; 38 | var schemeProvider = services.GetRequiredService(); 39 | Assert.NotNull(schemeProvider); 40 | 41 | var scheme = await schemeProvider.GetDefaultAuthenticateSchemeAsync(); 42 | Assert.NotNull(scheme); 43 | Assert.Equal(typeof(ApiKeyInRouteValuesHandler), scheme.HandlerType); 44 | 45 | var apiKeyOptionsSnapshot = services.GetService>(); 46 | var apiKeyOptions = apiKeyOptionsSnapshot?.Get(scheme.Name); 47 | Assert.NotNull(apiKeyOptions); 48 | Assert.NotNull(apiKeyOptions.Events?.OnValidateKey); 49 | Assert.Null(apiKeyOptions.ApiKeyProviderType); 50 | 51 | var apiKeyProvider = services.GetService(); 52 | Assert.Null(apiKeyProvider); 53 | } 54 | 55 | [Fact] 56 | public async Task TApiKeyProvider_Verify_Handler() 57 | { 58 | var services = _serverWithProvider.Host.Services; 59 | var schemeProvider = services.GetRequiredService(); 60 | Assert.NotNull(schemeProvider); 61 | 62 | var scheme = await schemeProvider.GetDefaultAuthenticateSchemeAsync(); 63 | Assert.NotNull(scheme); 64 | Assert.Equal(typeof(ApiKeyInRouteValuesHandler), scheme.HandlerType); 65 | 66 | var apiKeyOptionsSnapshot = services.GetService>(); 67 | var apiKeyOptions = apiKeyOptionsSnapshot?.Get(scheme.Name); 68 | Assert.NotNull(apiKeyOptions); 69 | Assert.Null(apiKeyOptions.Events?.OnValidateKey); 70 | Assert.NotNull(apiKeyOptions.ApiKeyProviderType); 71 | Assert.Equal(typeof(FakeApiKeyProvider), apiKeyOptions.ApiKeyProviderType); 72 | 73 | var apiKeyProvider = services.GetService(); 74 | Assert.NotNull(apiKeyProvider); 75 | Assert.Equal(typeof(FakeApiKeyProvider), apiKeyProvider.GetType()); 76 | } 77 | 78 | [Fact] 79 | public async Task Verify_challenge_www_authenticate_header() 80 | { 81 | using var response = await _client.GetAsync(TestServerBuilder.BaseUrl); 82 | Assert.False(response.IsSuccessStatusCode); 83 | 84 | var wwwAuthenticateHeader = response.Headers.WwwAuthenticate; 85 | Assert.NotEmpty(wwwAuthenticateHeader); 86 | 87 | var wwwAuthenticateHeaderToMatch = Assert.Single(wwwAuthenticateHeader); 88 | Assert.NotNull(wwwAuthenticateHeaderToMatch); 89 | Assert.Equal(ApiKeyDefaults.AuthenticationScheme, wwwAuthenticateHeaderToMatch.Scheme); 90 | Assert.Equal($"realm=\"{TestServerBuilder.Realm}\", charset=\"UTF-8\", in=\"route_values\", key_name=\"{FakeApiKeys.KeyName}\"", wwwAuthenticateHeaderToMatch.Parameter); 91 | } 92 | 93 | [Fact] 94 | public async Task Unauthorized() 95 | { 96 | using var request = new HttpRequestMessage(HttpMethod.Get, TestServerBuilder.BaseUrl); 97 | using var response = await _client.SendAsync(request); 98 | Assert.False(response.IsSuccessStatusCode); 99 | Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); 100 | } 101 | 102 | [Fact] 103 | public async Task Success() 104 | { 105 | var uri = $"{TestServerBuilder.BaseUrl}route/{FakeApiKeys.FakeKey}"; 106 | using var request = new HttpRequestMessage(HttpMethod.Get, uri); 107 | using var response = await _client.SendAsync(request); 108 | Assert.True(response.IsSuccessStatusCode); 109 | Assert.Equal(HttpStatusCode.OK, response.StatusCode); 110 | } 111 | 112 | [Fact] 113 | public async Task Invalid_key_unauthorized() 114 | { 115 | var uri = $"{TestServerBuilder.BaseUrl}route/{FakeApiKeys.FakeInvalidKey}"; 116 | using var request = new HttpRequestMessage(HttpMethod.Get, uri); 117 | using var response = await _client.SendAsync(request); 118 | Assert.False(response.IsSuccessStatusCode); 119 | Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); 120 | } 121 | 122 | 123 | 124 | [Fact] 125 | public async Task TApiKeyProvider_Unauthorized() 126 | { 127 | using var request = new HttpRequestMessage(HttpMethod.Get, TestServerBuilder.BaseUrl); 128 | using var response = await _clientWithProvider.SendAsync(request); 129 | Assert.False(response.IsSuccessStatusCode); 130 | Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); 131 | } 132 | 133 | [Fact] 134 | public async Task TApiKeyProvider_success() 135 | { 136 | var uri = $"{TestServerBuilder.BaseUrl}route/{FakeApiKeys.FakeKey}"; 137 | using var request = new HttpRequestMessage(HttpMethod.Get, uri); 138 | using var response = await _clientWithProvider.SendAsync(request); 139 | Assert.True(response.IsSuccessStatusCode); 140 | Assert.Equal(HttpStatusCode.OK, response.StatusCode); 141 | } 142 | 143 | [Fact] 144 | public async Task TApiKeyProvider_invalid_key_unauthotized() 145 | { 146 | var uri = $"{TestServerBuilder.BaseUrl}route/{FakeApiKeys.FakeInvalidKey}"; 147 | using var request = new HttpRequestMessage(HttpMethod.Get, uri); 148 | using var response = await _clientWithProvider.SendAsync(request); 149 | Assert.False(response.IsSuccessStatusCode); 150 | Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); 151 | } 152 | 153 | protected virtual void Dispose(bool disposing) 154 | { 155 | if (!_disposedValue) 156 | { 157 | if (disposing) 158 | { 159 | // dispose managed state (managed objects) 160 | 161 | _client?.Dispose(); 162 | _server?.Dispose(); 163 | 164 | _clientWithProvider?.Dispose(); 165 | _serverWithProvider?.Dispose(); 166 | } 167 | 168 | // free unmanaged resources (unmanaged objects) and override finalizer 169 | // set large fields to null 170 | _disposedValue = true; 171 | } 172 | } 173 | 174 | // // override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources 175 | // ~ApiKeyInRouteValuesHandlerTests() 176 | // { 177 | // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 178 | // Dispose(disposing: false); 179 | // } 180 | 181 | public void Dispose() 182 | { 183 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 184 | Dispose(disposing: true); 185 | GC.SuppressFinalize(this); 186 | } 187 | } 188 | } 189 | #endif -------------------------------------------------------------------------------- /test/AspNetCore.Authentication.ApiKey.Tests/ApiKeyPostConfigureOptionsTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using AspNetCore.Authentication.ApiKey.Tests.Infrastructure; 5 | using Xunit; 6 | 7 | namespace AspNetCore.Authentication.ApiKey.Tests 8 | { 9 | public class ApiKeyPostConfigureOptionsTests 10 | { 11 | static readonly string KeyName = "X-API-KEY"; 12 | 13 | [Fact] 14 | public async Task PostConfigure_no_option_set_throws_exception() 15 | { 16 | await Assert.ThrowsAsync(() => RunAuthInitAsync(_ => { })); 17 | } 18 | 19 | [Fact] 20 | public async Task PostConfigure_Realm_or_SuppressWWWAuthenticateHeader_not_set_throws_exception() 21 | { 22 | var exception = await Assert.ThrowsAsync(() => 23 | RunAuthInitWithProviderAsync(options => 24 | { 25 | options.KeyName = KeyName; 26 | }) 27 | ); 28 | 29 | Assert.Contains($"{nameof(ApiKeyOptions.Realm)} must be set in {typeof(ApiKeyOptions).Name} when setting up the authentication.", exception.Message); 30 | } 31 | 32 | [Fact] 33 | public async Task PostConfigure_Realm_not_set_but_SuppressWWWAuthenticateHeader_set_no_exception_thrown() 34 | { 35 | await RunAuthInitWithProviderAsync(options => 36 | { 37 | options.SuppressWWWAuthenticateHeader = true; 38 | options.KeyName = KeyName; 39 | }); 40 | } 41 | 42 | [Fact] 43 | public async Task PostConfigure_Realm_set_but_SuppressWWWAuthenticateHeader_not_set_no_exception_thrown() 44 | { 45 | await RunAuthInitWithProviderAsync(options => 46 | { 47 | options.Realm = "Test"; 48 | options.KeyName = KeyName; 49 | }); 50 | } 51 | 52 | [Fact] 53 | public async Task PostConfigure_KeyName_not_set_throws_exception() 54 | { 55 | var exception = await Assert.ThrowsAsync(() => 56 | RunAuthInitWithProviderAsync(options => 57 | { 58 | options.SuppressWWWAuthenticateHeader = true; 59 | }) 60 | ); 61 | 62 | Assert.Contains($"{nameof(ApiKeyOptions.KeyName)} must be set in {typeof(ApiKeyOptions).Name} when setting up the authentication.", exception.Message); 63 | } 64 | 65 | [Fact] 66 | public async Task PostConfigure_KeyName_set_no_exception_thrown() 67 | { 68 | await RunAuthInitWithProviderAsync(options => 69 | { 70 | options.SuppressWWWAuthenticateHeader = true; 71 | options.KeyName = KeyName; 72 | }); 73 | } 74 | 75 | [Fact] 76 | public async Task PostConfigure_Events_OnValidateKey_or_IApiKeyProvider_not_set_throws_exception() 77 | { 78 | var exception = await Assert.ThrowsAsync(() => 79 | RunAuthInitAsync(options => 80 | { 81 | options.SuppressWWWAuthenticateHeader = true; 82 | options.KeyName = KeyName; 83 | }) 84 | ); 85 | 86 | Assert.Contains($"Either {nameof(ApiKeyOptions.Events.OnValidateKey)} delegate on configure options {nameof(ApiKeyOptions.Events)} should be set or use an extention method with type parameter of type {nameof(IApiKeyProvider)}.", exception.Message); 87 | } 88 | 89 | [Fact] 90 | public async Task PostConfigure_Events_OnValidateKey_set_but_IApiKeyProvider_not_set_no_exception_thrown() 91 | { 92 | await RunAuthInitAsync(options => 93 | { 94 | options.Events.OnValidateKey = _ => Task.CompletedTask; 95 | options.SuppressWWWAuthenticateHeader = true; 96 | options.KeyName = KeyName; 97 | }); 98 | } 99 | 100 | [Fact] 101 | public async Task PostConfigure_Events_OnValidateKey_not_set_but_IApiKeyProvider_set_no_exception_thrown() 102 | { 103 | await RunAuthInitWithProviderAsync(options => 104 | { 105 | options.SuppressWWWAuthenticateHeader = true; 106 | options.KeyName = KeyName; 107 | }); 108 | } 109 | 110 | 111 | private static async Task RunAuthInitAsync(Action configureOptions) 112 | { 113 | var server = TestServerBuilder.BuildInHeaderOrQueryParamsServer(configureOptions); 114 | await server.CreateClient().GetAsync(TestServerBuilder.BaseUrl); 115 | } 116 | 117 | private static async Task RunAuthInitWithProviderAsync(Action configureOptions) 118 | { 119 | var server = TestServerBuilder.BuildInHeaderOrQueryParamsServerWithProvider(configureOptions); 120 | await server.CreateClient().GetAsync(TestServerBuilder.BaseUrl); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /test/AspNetCore.Authentication.ApiKey.Tests/ApiKeyUtilsTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using System.Security.Claims; 5 | using Xunit; 6 | 7 | namespace AspNetCore.Authentication.ApiKey.Tests 8 | { 9 | public class ApiKeyUtilsTests 10 | { 11 | [Fact] 12 | public static void BuildClaimsPrincipal_null_ownerName_no_exception() 13 | { 14 | var claimsPrincipal = ApiKeyUtils.BuildClaimsPrincipal(null, "Test", "Test", null); 15 | Assert.NotNull(claimsPrincipal); 16 | } 17 | 18 | [Fact] 19 | public static void BuildClaimsPrincipal_null_schemeName_throws_ArgumentNullException() 20 | { 21 | var exception = Assert.Throws(() => ApiKeyUtils.BuildClaimsPrincipal(null, null!, null, null)); 22 | Assert.Contains("schemeName", exception.Message); 23 | } 24 | 25 | [Fact] 26 | public static void BuildClaimsPrincipal_null_claimsIssuer_no_exception() 27 | { 28 | var claimsPrincipal = ApiKeyUtils.BuildClaimsPrincipal(null, "Test", null, null); 29 | Assert.NotNull(claimsPrincipal); 30 | } 31 | 32 | [Fact] 33 | public static void BuildClaimsPrincipal_null_claims_no_exception() 34 | { 35 | var claimsPrincipal = ApiKeyUtils.BuildClaimsPrincipal(null, "Test", null, null); 36 | Assert.NotNull(claimsPrincipal); 37 | } 38 | 39 | [Fact] 40 | public static void BuildClaimsPrincipal_adds_single_identity_without_claims() 41 | { 42 | var schemeName = "Test"; 43 | var claimsPrincipal = ApiKeyUtils.BuildClaimsPrincipal(null, schemeName, null, null); 44 | Assert.NotNull(claimsPrincipal); 45 | Assert.Single(claimsPrincipal.Identities); 46 | Assert.NotNull(claimsPrincipal.Identity); 47 | Assert.Equal(schemeName, claimsPrincipal.Identity.AuthenticationType); 48 | Assert.Empty(claimsPrincipal.Claims); 49 | } 50 | 51 | [Fact] 52 | public static void BuildClaimsPrincipal_adds_single_identity_with_claims() 53 | { 54 | var schemeName = "Test"; 55 | var claims = new List 56 | { 57 | new(ClaimTypes.Email, "abc@xyz.com") , 58 | new(ClaimTypes.Role, "admin") 59 | }; 60 | var claimsPrincipal = ApiKeyUtils.BuildClaimsPrincipal(null, schemeName, null, claims); 61 | Assert.NotNull(claimsPrincipal); 62 | Assert.Single(claimsPrincipal.Identities); 63 | Assert.NotNull(claimsPrincipal.Identity); 64 | Assert.Equal(schemeName, claimsPrincipal.Identity.AuthenticationType); 65 | Assert.NotEmpty(claimsPrincipal.Claims); 66 | Assert.Equal(claims.Count, claimsPrincipal.Claims.Count()); 67 | } 68 | 69 | [Fact] 70 | public static void BuildClaimsPrincipal_ownerName_adds_Name_and_NameIdentifier_claims() 71 | { 72 | var ownerName = "Test"; 73 | var schemeName = "Test"; 74 | var claimsPrincipal = ApiKeyUtils.BuildClaimsPrincipal(ownerName, schemeName, null, null); 75 | Assert.NotNull(claimsPrincipal); 76 | Assert.NotEmpty(claimsPrincipal.Claims); 77 | Assert.Equal(2, claimsPrincipal.Claims.Count()); 78 | Assert.Contains(claimsPrincipal.Claims, c => c.Type == ClaimTypes.NameIdentifier && c.Value == ownerName); 79 | Assert.Contains(claimsPrincipal.Claims, c => c.Type == ClaimTypes.Name && c.Value == ownerName); 80 | } 81 | 82 | [Fact] 83 | public static void BuildClaimsPrincipal_ownerName_adds_Name_and_NameIdentifier_claims2() 84 | { 85 | var ownerName = "Test"; 86 | var schemeName = "Test"; 87 | var claims = new List 88 | { 89 | new(ClaimTypes.Email, "abc@xyz.com") , 90 | new(ClaimTypes.Role, "admin") 91 | }; 92 | var claimsPrincipal = ApiKeyUtils.BuildClaimsPrincipal(ownerName, schemeName, null, claims); 93 | Assert.NotNull(claimsPrincipal); 94 | Assert.NotEmpty(claimsPrincipal.Claims); 95 | Assert.NotEqual(claims.Count, claimsPrincipal.Claims.Count()); 96 | Assert.Equal(claims.Count + 2, claimsPrincipal.Claims.Count()); 97 | Assert.Contains(claimsPrincipal.Claims, c => c.Type == ClaimTypes.NameIdentifier && c.Value == ownerName); 98 | Assert.Contains(claimsPrincipal.Claims, c => c.Type == ClaimTypes.Name && c.Value == ownerName); 99 | } 100 | 101 | [Fact] 102 | public static void BuildClaimsPrincipal_ownerName_adds_Name_and_NameIdentifier_claims_if_not_already_exists() 103 | { 104 | var ownerName = "Test"; 105 | var schemeName = "Test"; 106 | var claims = new List 107 | { 108 | new(ClaimTypes.Name, "Admin"), 109 | new(ClaimTypes.Role, "admin") 110 | }; 111 | var claimsPrincipal = ApiKeyUtils.BuildClaimsPrincipal(ownerName, schemeName, null, claims); 112 | Assert.NotNull(claimsPrincipal); 113 | Assert.NotEmpty(claimsPrincipal.Claims); 114 | Assert.NotEqual(claims.Count, claimsPrincipal.Claims.Count()); 115 | Assert.Equal(claims.Count + 1, claimsPrincipal.Claims.Count()); 116 | Assert.Contains(claimsPrincipal.Claims, c => c.Type == ClaimTypes.NameIdentifier && c.Value == ownerName); 117 | Assert.Contains(claimsPrincipal.Claims, c => c.Type == ClaimTypes.Name && c.Value != ownerName); 118 | Assert.Contains(claimsPrincipal.Claims, c => c.Type == ClaimTypes.Name && c.Value == "Admin"); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /test/AspNetCore.Authentication.ApiKey.Tests/AspNetCore.Authentication.ApiKey.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0;net8.0;net7.0;net6.0;net5.0;netcoreapp3.1;netcoreapp3.0;netcoreapp2.1;net461 5 | enable 6 | enable 7 | false 8 | latest 9 | true 10 | 11 | 12 | 13 | true 14 | 15 | 16 | 17 | 18 | true 19 | $(SolutionDir)key.snk 20 | false 21 | 22 | 23 | 24 | 25 | 26 | runtime; build; native; contentfiles; analyzers; buildtransitive 27 | all 28 | 29 | 30 | 31 | 32 | 33 | 34 | runtime; build; native; contentfiles; analyzers; buildtransitive 35 | all 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | runtime; build; native; contentfiles; analyzers; buildtransitive 45 | all 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | runtime; build; native; contentfiles; analyzers; buildtransitive 54 | all 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | runtime; build; native; contentfiles; analyzers; buildtransitive 63 | all 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | runtime; build; native; contentfiles; analyzers; buildtransitive 72 | all 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | runtime; build; native; contentfiles; analyzers; buildtransitive 81 | all 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | runtime; build; native; contentfiles; analyzers; buildtransitive 90 | all 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | runtime; build; native; contentfiles; analyzers; buildtransitive 103 | all 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | runtime; build; native; contentfiles; analyzers; buildtransitive 115 | all 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /test/AspNetCore.Authentication.ApiKey.Tests/Events/ApiKeyAuthenticationFailedContextTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using AspNetCore.Authentication.ApiKey.Tests.Infrastructure; 5 | using System.Net; 6 | using System.Net.Http; 7 | using Xunit; 8 | 9 | namespace AspNetCore.Authentication.ApiKey.Tests.Events 10 | { 11 | public class ApiKeyAuthenticationFailedContext 12 | { 13 | private static readonly string ExpectedExceptionMessage = $"Either {nameof(ApiKeyEvents.OnValidateKey)} delegate on configure options {nameof(ApiKeyOptions.Events)} should be set or use an extention method with type parameter of type {nameof(IApiKeyProvider)}."; 14 | 15 | [Fact] 16 | public async Task Exception_result_null() 17 | { 18 | using var server = TestServerBuilder.BuildInHeaderOrQueryParamsServer(options => 19 | { 20 | options.Realm = TestServerBuilder.Realm; 21 | options.KeyName = FakeApiKeys.KeyName; 22 | options.Events.OnValidateKey = context => 23 | { 24 | Assert.Null(context.Result); 25 | 26 | return Task.CompletedTask; 27 | }; 28 | 29 | options.Events.OnAuthenticationFailed = context => 30 | { 31 | Assert.Null(context.Result); 32 | Assert.NotNull(context.Exception); 33 | Assert.IsType(context.Exception); 34 | Assert.Equal(ExpectedExceptionMessage, context.Exception.Message); 35 | 36 | return Task.CompletedTask; 37 | }; 38 | }); 39 | using var client = server.CreateClient(); 40 | using var request = new HttpRequestMessage(HttpMethod.Get, TestServerBuilder.BaseUrl); 41 | request.Headers.Add(FakeApiKeys.KeyName, FakeApiKeys.FakeKey); 42 | 43 | var exception = await Assert.ThrowsAsync(async () => 44 | { 45 | using var response = await client.SendAsync(request); 46 | 47 | Assert.False(response.IsSuccessStatusCode); 48 | Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); 49 | }); 50 | 51 | Assert.Equal(ExpectedExceptionMessage, exception.Message); 52 | } 53 | 54 | 55 | [Fact] 56 | public async Task Exception_result_not_null() 57 | { 58 | using var server = TestServerBuilder.BuildInHeaderOrQueryParamsServer(options => 59 | { 60 | options.Realm = TestServerBuilder.Realm; 61 | options.KeyName = FakeApiKeys.KeyName; 62 | options.Events.OnValidateKey = context => 63 | { 64 | Assert.Null(context.Result); 65 | 66 | return Task.CompletedTask; 67 | }; 68 | 69 | options.Events.OnAuthenticationFailed = context => 70 | { 71 | Assert.Null(context.Result); 72 | Assert.NotNull(context.Exception); 73 | Assert.IsType(context.Exception); 74 | Assert.Equal(ExpectedExceptionMessage, context.Exception.Message); 75 | 76 | context.NoResult(); 77 | 78 | Assert.NotNull(context.Result); 79 | 80 | return Task.CompletedTask; 81 | }; 82 | }); 83 | using var client = server.CreateClient(); 84 | using var request = new HttpRequestMessage(HttpMethod.Get, TestServerBuilder.BaseUrl); 85 | request.Headers.Add(FakeApiKeys.KeyName, FakeApiKeys.FakeKey); 86 | using var response = await client.SendAsync(request); 87 | 88 | Assert.False(response.IsSuccessStatusCode); 89 | Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /test/AspNetCore.Authentication.ApiKey.Tests/Events/ApiKeyAuthenticationSucceededContextTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using AspNetCore.Authentication.ApiKey.Tests.Infrastructure; 5 | using Microsoft.AspNetCore.TestHost; 6 | using System.Net; 7 | using System.Net.Http; 8 | using System.Security.Claims; 9 | using System.Text.Json; 10 | using Xunit; 11 | 12 | namespace AspNetCore.Authentication.ApiKey.Tests.Events 13 | { 14 | public class ApiKeyAuthenticationSucceededContextTests : IDisposable 15 | { 16 | private readonly List _serversToDispose = []; 17 | private bool _disposedValue; 18 | 19 | [Fact] 20 | public async Task Principal_not_null() 21 | { 22 | using var client = BuildClient( 23 | context => 24 | { 25 | Assert.NotNull(context.Principal); 26 | Assert.Null(context.Result); 27 | return Task.CompletedTask; 28 | } 29 | ); 30 | 31 | var principal = await ApiKeyAuthenticationSucceededContextTests.RunSuccessTests(client); 32 | Assert.True(principal.Identity.IsAuthenticated); 33 | } 34 | 35 | [Fact] 36 | public async Task ReplacePrincipal_null_throws_argument_null_exception() 37 | { 38 | using var client = BuildClient( 39 | context => 40 | { 41 | Assert.Throws(() => context.ReplacePrincipal(null!)); 42 | return Task.CompletedTask; 43 | } 44 | ); 45 | 46 | await ApiKeyAuthenticationSucceededContextTests.RunSuccessTests(client); 47 | } 48 | 49 | [Fact] 50 | public async Task ReplacePrincipal() 51 | { 52 | using var client = BuildClient( 53 | context => 54 | { 55 | var newPrincipal = new ClaimsPrincipal(); 56 | context.ReplacePrincipal(newPrincipal); 57 | 58 | Assert.NotNull(context.Principal); 59 | Assert.Equal(newPrincipal, context.Principal); 60 | 61 | return Task.CompletedTask; 62 | } 63 | ); 64 | 65 | await ApiKeyAuthenticationSucceededContextTests.RunUnauthorizedTests(client); 66 | } 67 | 68 | [Fact] 69 | public async Task RejectPrincipal() 70 | { 71 | using var client = BuildClient( 72 | context => 73 | { 74 | context.RejectPrincipal(); 75 | 76 | Assert.Null(context.Principal); 77 | 78 | return Task.CompletedTask; 79 | } 80 | ); 81 | 82 | await ApiKeyAuthenticationSucceededContextTests.RunUnauthorizedTests(client); 83 | } 84 | 85 | [Fact] 86 | public async Task AddClaim() 87 | { 88 | var claim = new Claim(ClaimTypes.Actor, "Actor"); 89 | 90 | using var client = BuildClient( 91 | context => 92 | { 93 | context.AddClaim(claim); 94 | 95 | Assert.NotNull(context.Principal); 96 | Assert.Contains(context.Principal.Claims, c => c.Type == claim.Type && c.Value == claim.Value); 97 | 98 | return Task.CompletedTask; 99 | } 100 | ); 101 | 102 | var principal = await ApiKeyAuthenticationSucceededContextTests.RunSuccessTests(client); 103 | Assert.Contains(new ClaimDto(claim), principal.Claims); 104 | } 105 | 106 | [Fact] 107 | public async Task AddClaims() 108 | { 109 | var claims = new List{ 110 | new(ClaimTypes.Actor, "Actor"), 111 | new(ClaimTypes.Country, "Country") 112 | }; 113 | 114 | using var client = BuildClient( 115 | context => 116 | { 117 | context.AddClaims(claims); 118 | 119 | Assert.NotNull(context.Principal); 120 | Assert.Contains(context.Principal.Claims, c => c.Type == claims[0].Type && c.Value == claims[0].Value); 121 | Assert.Contains(context.Principal.Claims, c => c.Type == claims[1].Type && c.Value == claims[1].Value); 122 | 123 | return Task.CompletedTask; 124 | } 125 | ); 126 | 127 | var principal = await ApiKeyAuthenticationSucceededContextTests.RunSuccessTests(client); 128 | Assert.Contains(new ClaimDto(claims[0]), principal.Claims); 129 | Assert.Contains(new ClaimDto(claims[1]), principal.Claims); 130 | } 131 | 132 | 133 | 134 | private HttpClient BuildClient(Func onAuthenticationSucceeded) 135 | { 136 | var server = TestServerBuilder.BuildInHeaderOrQueryParamsServerWithProvider(options => 137 | { 138 | options.KeyName = FakeApiKeys.KeyName; 139 | options.Realm = TestServerBuilder.Realm; 140 | options.Events.OnAuthenticationSucceeded = onAuthenticationSucceeded; 141 | }); 142 | 143 | _serversToDispose.Add(server); 144 | return server.CreateClient(); 145 | } 146 | 147 | private static async Task RunUnauthorizedTests(HttpClient client) 148 | { 149 | using var request = new HttpRequestMessage(HttpMethod.Get, TestServerBuilder.ClaimsPrincipalUrl); 150 | request.Headers.Add(FakeApiKeys.KeyName, FakeApiKeys.FakeKey); 151 | using var response_unauthorized = await client.SendAsync(request); 152 | Assert.False(response_unauthorized.IsSuccessStatusCode); 153 | Assert.Equal(HttpStatusCode.Unauthorized, response_unauthorized.StatusCode); 154 | } 155 | 156 | private static async Task RunSuccessTests(HttpClient client) 157 | { 158 | using var request = new HttpRequestMessage(HttpMethod.Get, TestServerBuilder.ClaimsPrincipalUrl); 159 | request.Headers.Add(FakeApiKeys.KeyName, FakeApiKeys.FakeKey); 160 | using var response_ok = await client.SendAsync(request); 161 | Assert.True(response_ok.IsSuccessStatusCode); 162 | Assert.Equal(HttpStatusCode.OK, response_ok.StatusCode); 163 | 164 | var content = await response_ok.Content.ReadAsStringAsync(); 165 | Assert.False(string.IsNullOrWhiteSpace(content)); 166 | return JsonSerializer.Deserialize(content); 167 | } 168 | 169 | protected virtual void Dispose(bool disposing) 170 | { 171 | if (!_disposedValue) 172 | { 173 | if (disposing) 174 | { 175 | // TODO: dispose managed state (managed objects) 176 | 177 | _serversToDispose.ForEach(s => s.Dispose()); 178 | } 179 | 180 | // TODO: free unmanaged resources (unmanaged objects) and override finalizer 181 | // TODO: set large fields to null 182 | _disposedValue = true; 183 | } 184 | } 185 | 186 | // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources 187 | // ~ApiKeyAuthenticationSucceededContextTests() 188 | // { 189 | // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 190 | // Dispose(disposing: false); 191 | // } 192 | 193 | public void Dispose() 194 | { 195 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 196 | Dispose(disposing: true); 197 | GC.SuppressFinalize(this); 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /test/AspNetCore.Authentication.ApiKey.Tests/Events/ApiKeyHandleChallengeContextTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using AspNetCore.Authentication.ApiKey.Tests.Infrastructure; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.AspNetCore.TestHost; 7 | using System.Net; 8 | using System.Net.Http; 9 | using Xunit; 10 | 11 | namespace AspNetCore.Authentication.ApiKey.Tests.Events 12 | { 13 | public class ApiKeyHandleChallengeContextTests : IDisposable 14 | { 15 | private readonly List _serversToDispose = []; 16 | private bool _disposedValue; 17 | 18 | [Fact] 19 | public async Task Handled() 20 | { 21 | using var client = BuildClient( 22 | context => 23 | { 24 | Assert.False(context.IsHandled); 25 | 26 | context.Response.StatusCode = StatusCodes.Status400BadRequest; 27 | context.Handled(); 28 | 29 | Assert.True(context.IsHandled); 30 | 31 | return Task.CompletedTask; 32 | } 33 | ); 34 | 35 | using var response = await client.GetAsync(TestServerBuilder.BaseUrl); 36 | 37 | Assert.False(response.IsSuccessStatusCode); 38 | Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); 39 | } 40 | 41 | [Fact] 42 | public async Task Handled_not_called() 43 | { 44 | using var client = BuildClient( 45 | context => 46 | { 47 | Assert.False(context.IsHandled); 48 | 49 | context.Response.StatusCode = StatusCodes.Status400BadRequest; 50 | 51 | return Task.CompletedTask; 52 | } 53 | ); 54 | 55 | using var response = await client.GetAsync(TestServerBuilder.BaseUrl); 56 | 57 | Assert.False(response.IsSuccessStatusCode); 58 | Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); 59 | } 60 | 61 | 62 | 63 | private HttpClient BuildClient(Func onHandleChallenge) 64 | { 65 | var server = TestServerBuilder.BuildInHeaderOrQueryParamsServerWithProvider(options => 66 | { 67 | options.KeyName = FakeApiKeys.KeyName; 68 | options.Realm = TestServerBuilder.Realm; 69 | options.Events.OnHandleChallenge = onHandleChallenge; 70 | }); 71 | 72 | _serversToDispose.Add(server); 73 | return server.CreateClient(); 74 | } 75 | 76 | protected virtual void Dispose(bool disposing) 77 | { 78 | if (!_disposedValue) 79 | { 80 | if (disposing) 81 | { 82 | // TODO: dispose managed state (managed objects) 83 | 84 | _serversToDispose.ForEach(s => s.Dispose()); 85 | } 86 | 87 | // TODO: free unmanaged resources (unmanaged objects) and override finalizer 88 | // TODO: set large fields to null 89 | _disposedValue = true; 90 | } 91 | } 92 | 93 | // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources 94 | // ~ApiKeyHandleChallengeContextTests() 95 | // { 96 | // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 97 | // Dispose(disposing: false); 98 | // } 99 | 100 | public void Dispose() 101 | { 102 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 103 | Dispose(disposing: true); 104 | GC.SuppressFinalize(this); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /test/AspNetCore.Authentication.ApiKey.Tests/Events/ApiKeyHandleForbiddenContextTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using AspNetCore.Authentication.ApiKey.Tests.Infrastructure; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.AspNetCore.TestHost; 7 | using System.Net; 8 | using System.Net.Http; 9 | using Xunit; 10 | 11 | namespace AspNetCore.Authentication.ApiKey.Tests.Events 12 | { 13 | public class ApiKeyHandleForbiddenContextTests : IDisposable 14 | { 15 | private readonly List _serversToDispose = []; 16 | private bool _disposedValue; 17 | 18 | [Fact] 19 | public async Task Handled() 20 | { 21 | using var client = BuildClient( 22 | context => 23 | { 24 | Assert.False(context.IsHandled); 25 | 26 | context.Response.StatusCode = StatusCodes.Status400BadRequest; 27 | context.Handled(); 28 | 29 | Assert.True(context.IsHandled); 30 | 31 | return Task.CompletedTask; 32 | } 33 | ); 34 | 35 | using var request = new HttpRequestMessage(HttpMethod.Get, TestServerBuilder.ForbiddenUrl); 36 | request.Headers.Add(FakeApiKeys.KeyName, FakeApiKeys.FakeKey); 37 | using var response = await client.SendAsync(request); 38 | 39 | Assert.False(response.IsSuccessStatusCode); 40 | Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); 41 | } 42 | 43 | [Fact] 44 | public async Task Handled_not_called() 45 | { 46 | using var client = BuildClient( 47 | context => 48 | { 49 | Assert.False(context.IsHandled); 50 | 51 | context.Response.StatusCode = StatusCodes.Status400BadRequest; 52 | 53 | return Task.CompletedTask; 54 | } 55 | ); 56 | 57 | using var request = new HttpRequestMessage(HttpMethod.Get, TestServerBuilder.ForbiddenUrl); 58 | request.Headers.Add(FakeApiKeys.KeyName, FakeApiKeys.FakeKey); 59 | using var response = await client.SendAsync(request); 60 | 61 | Assert.False(response.IsSuccessStatusCode); 62 | Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); 63 | } 64 | 65 | 66 | 67 | private HttpClient BuildClient(Func onHandleForbidden) 68 | { 69 | var server = TestServerBuilder.BuildInHeaderOrQueryParamsServerWithProvider(options => 70 | { 71 | options.KeyName = FakeApiKeys.KeyName; 72 | options.Realm = TestServerBuilder.Realm; 73 | options.Events.OnHandleForbidden = onHandleForbidden; 74 | }); 75 | 76 | _serversToDispose.Add(server); 77 | return server.CreateClient(); 78 | } 79 | 80 | protected virtual void Dispose(bool disposing) 81 | { 82 | if (!_disposedValue) 83 | { 84 | if (disposing) 85 | { 86 | // dispose managed state (managed objects) 87 | 88 | _serversToDispose.ForEach(s => s.Dispose()); 89 | } 90 | 91 | // free unmanaged resources (unmanaged objects) and override finalizer 92 | // set large fields to null 93 | _disposedValue = true; 94 | } 95 | } 96 | 97 | // // override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources 98 | // ~ApiKeyHandleForbiddenContextTests() 99 | // { 100 | // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 101 | // Dispose(disposing: false); 102 | // } 103 | 104 | public void Dispose() 105 | { 106 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 107 | Dispose(disposing: true); 108 | GC.SuppressFinalize(this); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /test/AspNetCore.Authentication.ApiKey.Tests/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "")] 9 | [assembly: SuppressMessage("Maintainability", "CA1510:Use ArgumentNullException throw helper", Justification = "")] 10 | -------------------------------------------------------------------------------- /test/AspNetCore.Authentication.ApiKey.Tests/Infrastructure/ClaimsPrincipalDto.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using System.Security.Claims; 5 | using System.Security.Principal; 6 | 7 | namespace AspNetCore.Authentication.ApiKey.Tests.Infrastructure 8 | { 9 | [Serializable] 10 | struct ClaimsPrincipalDto 11 | { 12 | public ClaimsPrincipalDto(ClaimsPrincipal user) 13 | { 14 | Identity = new ClaimsIdentityDto(user.Identity); 15 | Identities = user.Identities.Select(i => new ClaimsIdentityDto(i)); 16 | Claims = user.Claims.Select(c => new ClaimDto(c)); 17 | } 18 | 19 | public ClaimsIdentityDto Identity { get; set; } 20 | public IEnumerable Identities { get; private set; } 21 | public IEnumerable Claims { get; set; } 22 | } 23 | 24 | [Serializable] 25 | struct ClaimsIdentityDto 26 | { 27 | public ClaimsIdentityDto(IIdentity? identity) 28 | { 29 | if (identity == null) throw new ArgumentNullException(nameof(identity)); 30 | Name = identity.Name; 31 | IsAuthenticated = identity.IsAuthenticated; 32 | AuthenticationType = identity.AuthenticationType; 33 | } 34 | 35 | public string? Name { get; set; } 36 | public bool IsAuthenticated { get; set; } 37 | public string? AuthenticationType { get; set; } 38 | } 39 | 40 | [Serializable] 41 | struct ClaimDto 42 | { 43 | public ClaimDto(Claim claim) 44 | { 45 | Type = claim.Type; 46 | Value = claim.Value; 47 | Issuer = claim.Issuer; 48 | OriginalIssuer = claim.OriginalIssuer; 49 | } 50 | 51 | public string Type { get; set; } 52 | public string Value { get; set; } 53 | public string Issuer { get; set; } 54 | public string OriginalIssuer { get; set; } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/AspNetCore.Authentication.ApiKey.Tests/Infrastructure/FakeApiKeyProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Mihir Dilip. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE file in the project root for license information. 3 | 4 | using System.Security.Claims; 5 | 6 | namespace AspNetCore.Authentication.ApiKey.Tests.Infrastructure 7 | { 8 | class FakeApiKeyProvider : IApiKeyProvider 9 | { 10 | public Task ProvideAsync(string key) 11 | { 12 | var apiKey = FakeApiKeys.Keys.FirstOrDefault(k => k.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); 13 | if (apiKey != null) 14 | { 15 | if (apiKey.Key == FakeApiKeys.FakeKeyForLegacyIgnoreExtraValidatedApiKeyCheck) 16 | { 17 | // replace the key 18 | apiKey = new FakeApiKey(FakeApiKeys.FakeKey, apiKey.OwnerName, apiKey.Claims); 19 | } 20 | else if (apiKey.Key == FakeApiKeys.FakeKeyThrowsNotImplemented) 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | #if !(NET461 || NETSTANDARD2_0 || NETCOREAPP2_1) 25 | else if (apiKey.Key == FakeApiKeys.FakeKeyIgnoreAuthenticationIfAllowAnonymous) 26 | { 27 | throw new InvalidOperationException(nameof(ApiKeyOptions.IgnoreAuthenticationIfAllowAnonymous)); 28 | } 29 | #endif 30 | } 31 | return Task.FromResult(apiKey); 32 | } 33 | } 34 | 35 | class FakeApiKey : IApiKey 36 | { 37 | public FakeApiKey(string key, string ownerName, IReadOnlyCollection? claims = null) 38 | { 39 | Key = key; 40 | OwnerName = ownerName; 41 | Claims = claims ?? []; 42 | } 43 | 44 | public string Key { get; } 45 | 46 | public string OwnerName { get; } 47 | 48 | public IReadOnlyCollection Claims { get; } 49 | } 50 | 51 | class FakeApiKeys 52 | { 53 | internal const string KeyName = "X-API-KEY"; 54 | 55 | internal static string FakeInvalidKey = ""; 56 | internal static string FakeKey = "myrandomfakekey"; 57 | internal static string FakeKeyThrowsNotImplemented = "myrandomfakekey-not-implemented"; 58 | internal static string FakeKeyForLegacyIgnoreExtraValidatedApiKeyCheck = "ForLegacyIgnoreExtraValidatedApiKeyCheck"; 59 | internal static string FakeKeyIgnoreAuthenticationIfAllowAnonymous = "IgnoreAuthenticationIfAllowAnonymous"; 60 | internal static string FakeKeyOwner = "Fake Key"; 61 | internal static Claim FakeNameClaim = new(ClaimTypes.Name, "FakeNameClaim", ClaimValueTypes.String); 62 | internal static Claim FakeNameIdentifierClaim = new(ClaimTypes.NameIdentifier, "FakeNameIdentifierClaim", ClaimValueTypes.String); 63 | internal static Claim FakeRoleClaim = new(ClaimTypes.Role, "FakeRoleClaim", ClaimValueTypes.String); 64 | 65 | internal static List Keys => 66 | [ 67 | new FakeApiKey(FakeKey, FakeKeyOwner, [FakeNameClaim, FakeNameIdentifierClaim, FakeRoleClaim]), 68 | new FakeApiKey(FakeKeyThrowsNotImplemented, FakeKeyOwner, [FakeNameClaim, FakeNameIdentifierClaim, FakeRoleClaim]), 69 | new FakeApiKey(FakeKeyForLegacyIgnoreExtraValidatedApiKeyCheck, FakeKeyOwner, [FakeNameClaim, FakeNameIdentifierClaim, FakeRoleClaim]), 70 | new FakeApiKey(FakeKeyIgnoreAuthenticationIfAllowAnonymous, FakeKeyOwner, [FakeNameClaim, FakeNameIdentifierClaim, FakeRoleClaim]) 71 | ]; 72 | } 73 | } 74 | --------------------------------------------------------------------------------