Skip to content

Commit 43c1a95

Browse files
authored
Merge pull request #7 from mihirdilip/3.1.0
3.1.0
2 parents 84c5951 + 467d745 commit 43c1a95

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+2372
-220
lines changed

README.md

Lines changed: 70 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# AspNetCore.Authentication.ApiKey
2-
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 in Header, QueryParams or HeaderOrQueryParams.
2+
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, QueryParams or HeaderOrQueryParams.
33

44
[View On GitHub](https://github.com/mihirdilip/aspnetcore-authentication-apikey)
55

@@ -16,6 +16,8 @@ PM> Install-Package AspNetCore.Authentication.ApiKey
1616

1717
## Example Usage
1818

19+
Samples are available under [samples directory](samples).
20+
1921
Setting it up is quite simple. You will need basic working knowledge of ASP.NET Core 2.2 or newer to get started using this code.
2022

2123
On [**Startup.cs**](#startupcs), as shown below, add 2 lines in *ConfigureServices* method `services.AddAuthentication(ApiKeyDefaults.AuthenticationScheme).AddApiKeyInHeaderOrQueryParams<ApiKeyProvider>(options => { options.Realm = "My App"; options.KeyName = "X-API-KEY"; });`. And a line `app.UseAuthentication();` in *Configure* method.
@@ -30,39 +32,24 @@ Also add an implementation of *IApiKeyProvider* as shown below in [**ApiKeyProvi
3032
using AspNetCore.Authentication.ApiKey;
3133
public class Startup
3234
{
33-
public Startup(IConfiguration configuration)
34-
{
35-
Configuration = configuration;
36-
}
37-
38-
public IConfiguration Configuration { get; }
39-
4035
public void ConfigureServices(IServiceCollection services)
4136
{
42-
// Add the API Key authentication here..
43-
// AddApiKeyInHeaderOrQueryParams extension takes an implementation of IApiKeyProvider for validating the key.
44-
// It also requires Realm and KeyName to be set in the options.
37+
// Add the ApiKey scheme authentication here..
38+
// It requires Realm to be set in the options if SuppressWWWAuthenticateHeader is not set.
39+
// 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.
4540
services.AddAuthentication(ApiKeyDefaults.AuthenticationScheme)
46-
//// use below to accept API Key either in header or query parameter
47-
.AddApiKeyInHeaderOrQueryParams<ApiKeyProvider>(options =>
48-
{
49-
options.Realm = "My App";
50-
options.KeyName = "X-API-KEY"; // Your api key name which the clients will require to send the key.
51-
});
5241

53-
//// use below instead to only accept API Key in header
54-
//.AddApiKeyInHeader<ApiKeyProvider>(options =>
55-
//{
56-
// options.Realm = "My App";
57-
// options.KeyName = "X-API-KEY"; // Your api key name which the clients will require to send the key.
58-
//});
42+
// 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.
43+
// Please note if OnValidateKey delegete on options.Events is also set then this delegate will be used instead of ApiKeyProvider.*
44+
//.AddApiKeyInHeaderOrQueryParams(options =>
5945
60-
//// use below instead to only accept API Key in query parameter
61-
//.AddApiKeyQueryParams<ApiKeyProvider>(options =>
62-
//{
63-
// options.Realm = "My App";
64-
// options.KeyName = "X-API-KEY"; // Your api key name which the clients will require to send the key.
65-
//});
46+
// The below AddApiKeyInHeaderOrQueryParams with type parameter will add the ApiKeyProvider to the dependency register.
47+
// Please note if OnValidateKey delegete on options.Events is also set then this delegate will be used instead of ApiKeyProvider.
48+
.AddApiKeyInHeaderOrQueryParams<ApiKeyProvider>(options =>
49+
{
50+
options.Realm = "Sample Web API";
51+
options.KeyName = "X-API-KEY";
52+
});
6653

6754
services.AddControllers();
6855

@@ -99,39 +86,24 @@ public class Startup
9986
using AspNetCore.Authentication.ApiKey;
10087
public class Startup
10188
{
102-
public Startup(IConfiguration configuration)
103-
{
104-
Configuration = configuration;
105-
}
106-
107-
public IConfiguration Configuration { get; }
108-
10989
public void ConfigureServices(IServiceCollection services)
11090
{
111-
// Add the API Key authentication here..
112-
// AddApiKeyInHeaderOrQueryParams extension takes an implementation of IApiKeyProvider for validating the key.
113-
// It also requires Realm and KeyName to be set in the options.
91+
// Add the ApiKey scheme authentication here..
92+
// It requires Realm to be set in the options if SuppressWWWAuthenticateHeader is not set.
93+
// 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.
11494
services.AddAuthentication(ApiKeyDefaults.AuthenticationScheme)
115-
//// use below to accept API Key either in header or query parameter
116-
.AddApiKeyInHeaderOrQueryParams<ApiKeyProvider>(options =>
117-
{
118-
options.Realm = "My App";
119-
options.KeyName = "X-API-KEY"; // Your api key name which the clients will require to send the key.
120-
});
12195

122-
//// use below instead to only accept API Key in header
123-
//.AddApiKeyInHeader<ApiKeyProvider>(options =>
124-
//{
125-
// options.Realm = "My App";
126-
// options.KeyName = "X-API-KEY"; // Your api key name which the clients will require to send the key.
127-
//});
96+
// 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.
97+
// Please note if OnValidateKey delegete on options.Events is also set then this delegate will be used instead of ApiKeyProvider.*
98+
//.AddApiKeyInHeaderOrQueryParams(options =>
12899
129-
//// use below instead to only accept API Key in query parameter
130-
//.AddApiKeyInQueryParams<ApiKeyProvider>(options =>
131-
//{
132-
// options.Realm = "My App";
133-
// options.KeyName = "X-API-KEY"; // Your api key name which the clients will require to send the key.
134-
//});
100+
// The below AddApiKeyInHeaderOrQueryParams with type parameter will add the ApiKeyProvider to the dependency register.
101+
// Please note if OnValidateKey delegete on options.Events is also set then this delegate will be used instead of ApiKeyProvider.
102+
.AddApiKeyInHeaderOrQueryParams<ApiKeyProvider>(options =>
103+
{
104+
options.Realm = "Sample Web API";
105+
options.KeyName = "X-API-KEY";
106+
});
135107

136108
services.AddMvc();
137109

@@ -160,7 +132,7 @@ public class ApiKeyProvider : IApiKeyProvider
160132
{
161133
private readonly ILogger<ApiKeyProvider> _logger;
162134

163-
public BasicUserValidationService(ILogger<ApiKeyProvider> logger)
135+
public ApiKeyProvider(ILogger<ApiKeyProvider> logger)
164136
{
165137
_logger = logger;
166138
}
@@ -199,6 +171,46 @@ class ApiKey : IApiKey
199171
}
200172
```
201173

174+
## Configuration (ApiKeyOptions)
175+
#### KeyName
176+
Required to be set. It is the name of the header if it is setup as in-header or the name of the query parameter if set as in-query-string.
177+
178+
#### Realm
179+
Required to be set if SuppressWWWAuthenticateHeader is not set to true. It is used with WWW-Authenticate response header when challenging un-authenticated requests.
180+
181+
#### SuppressWWWAuthenticateHeader
182+
Default value is false.
183+
When set to true, it will NOT return WWW-Authenticate response header when challenging un-authenticated requests.
184+
When set to false, it will return WWW-Authenticate response header when challenging un-authenticated requests.
185+
186+
#### Events
187+
The object provided by the application to process events raised by the api key authentication middleware.
188+
The application may implement the interface fully, or it may create an instance of ApiKeyEvents and assign delegates only to the events it wants to process.
189+
- ##### OnValidateKey
190+
A delegate assigned to this property will be invoked just before validating the api key.
191+
You must provide a delegate for this property for authentication to occur.
192+
In your delegate you should either call context.ValidationSucceeded() which will handle construction of authentication principal from the api key which will be assiged the context.Principal property and call context.Success(), or construct an authentication principal from the api key & attach it to the context.Principal property and finally call context.Success() method.
193+
If only context.Principal property set without calling context.Success() method then, Success() method is automaticalled called.
194+
195+
- ##### OnAuthenticationSucceeded
196+
A delegate assigned to this property will be invoked when the authentication succeeds. It will not be called if OnValidateKey delegate is assigned.
197+
It can be used for adding claims, headers, etc to the response.
198+
199+
- ##### OnAuthenticationFailed
200+
A delegate assigned to this property will be invoked when the authentication fails.
201+
202+
- ##### OnHandleChallenge
203+
A delegate assigned to this property will be invoked before a challenge is sent back to the caller when handling unauthorized response.
204+
Only use this if you know what you are doing and if you want to use custom implementation. Set the delegate to deal with 401 challenge concerns, if an authentication scheme in question deals an authentication interaction as part of it's request flow. (like adding a response header, or changing the 401 result to 302 of a login page or external sign-in location.)
205+
Call context.Handled() at the end so that any default logic for this challenge will be skipped.
206+
207+
- ##### OnHandleForbidden
208+
A delegate assigned to this property will be invoked if Authorization fails and results in a Forbidden response.
209+
Only use this if you know what you are doing and if you want to use custom implementation.
210+
Set the delegate to handle Forbid.
211+
Call context.Handled() at the end so that any default logic will be skipped.
212+
213+
202214
## Additional Notes
203215
Please note that, by default, with ASP.NET Core, all the requests are not challenged for authentication. So don't worry if your *ApiKeyProvider* is not hit when you don't pass the required api key authentication details with the request. It is a normal behaviour. ASP.NET Core challenges authentication only when it is specifically told to do so either by decorating controller/method with *[Authorize]* filter attribute or by some other means.
204216

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using AspNetCore.Authentication.ApiKey;
2+
using System.Collections.Generic;
3+
using System.Security.Claims;
4+
5+
namespace SampleWebApi.Models
6+
{
7+
class ApiKey : IApiKey
8+
{
9+
public ApiKey(string key, string owner, List<Claim> claims = null)
10+
{
11+
Key = key;
12+
OwnerName = owner;
13+
Claims = claims ?? new List<Claim>();
14+
}
15+
16+
public string Key { get; }
17+
public string OwnerName { get; }
18+
public IReadOnlyCollection<Claim> Claims { get; }
19+
}
20+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using AspNetCore.Authentication.ApiKey;
2+
using System.Threading.Tasks;
3+
4+
namespace SampleWebApi.Repositories
5+
{
6+
/// <summary>
7+
/// NOTE: DO NOT USE THIS IMPLEMENTATION. THIS IS FOR DEMO PURPOSE ONLY
8+
/// </summary>
9+
public interface IApiKeyRepository
10+
{
11+
Task<IApiKey> GetApiKeyAsync(string key);
12+
}
13+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using AspNetCore.Authentication.ApiKey;
2+
using SampleWebApi.Models;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
8+
namespace SampleWebApi.Repositories
9+
{
10+
/// <summary>
11+
/// NOTE: DO NOT USE THIS IMPLEMENTATION. THIS IS FOR DEMO PURPOSE ONLY
12+
/// </summary>
13+
public class InMemoryApiKeyRepository : IApiKeyRepository
14+
{
15+
private List<IApiKey> _cache = new List<IApiKey>
16+
{
17+
new ApiKey("Key1", "Admin"),
18+
new ApiKey("Key2", "User"),
19+
};
20+
21+
public Task<IApiKey> GetApiKeyAsync(string key)
22+
{
23+
var apiKey = _cache.FirstOrDefault(k => k.Key.Equals(key, StringComparison.OrdinalIgnoreCase));
24+
return Task.FromResult(apiKey);
25+
}
26+
}
27+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<PropertyGroup>
4+
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
5+
<HasSharedItems>true</HasSharedItems>
6+
<SharedGUID>e544fb20-29f3-41f5-a78e-6164f9c43b3c</SharedGUID>
7+
</PropertyGroup>
8+
<PropertyGroup Label="Configuration">
9+
<Import_RootNamespace>SampleWebApi.Shared</Import_RootNamespace>
10+
</PropertyGroup>
11+
<ItemGroup>
12+
<Compile Include="$(MSBuildThisFileDirectory)Models\ApiKey.cs" />
13+
<Compile Include="$(MSBuildThisFileDirectory)Services\ApiKeyProvider.cs" />
14+
<Compile Include="$(MSBuildThisFileDirectory)Repositories\InMemoryApiKeyRepository.cs" />
15+
<Compile Include="$(MSBuildThisFileDirectory)Repositories\IApiKeyRepository.cs" />
16+
</ItemGroup>
17+
</Project>

0 commit comments

Comments
 (0)