Skip to content

Commit 12d59ea

Browse files
committed
Initial commit.
1 parent 6a09ce0 commit 12d59ea

13 files changed

+401
-0
lines changed

.appveyor.yaml

Whitespace-only changes.

Kaive.HttpClient.OAuth2Handler.sln

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio 15
4+
VisualStudioVersion = 15.0.28010.2003
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kaive.HttpClient.OAuth2Handler", "src\Kaive.HttpClient.OAuth2Handler\Kaive.HttpClient.OAuth2Handler.csproj", "{0625DC02-DE9E-40FF-9D92-9C9F8B344EDC}"
7+
EndProject
8+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C24B57E3-E01F-4109-A217-1773B633F907}"
9+
EndProject
10+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5D7F4F2B-E2F0-4E8F-9232-664E2F0B2879}"
11+
ProjectSection(SolutionItems) = preProject
12+
.appveyor.yaml = .appveyor.yaml
13+
.gitignore = .gitignore
14+
fastchannel.png = fastchannel.png
15+
LICENSE = LICENSE
16+
README.md = README.md
17+
EndProjectSection
18+
EndProject
19+
Global
20+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
21+
Debug|Any CPU = Debug|Any CPU
22+
Release|Any CPU = Release|Any CPU
23+
EndGlobalSection
24+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
25+
{0625DC02-DE9E-40FF-9D92-9C9F8B344EDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26+
{0625DC02-DE9E-40FF-9D92-9C9F8B344EDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
27+
{0625DC02-DE9E-40FF-9D92-9C9F8B344EDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
28+
{0625DC02-DE9E-40FF-9D92-9C9F8B344EDC}.Release|Any CPU.Build.0 = Release|Any CPU
29+
EndGlobalSection
30+
GlobalSection(SolutionProperties) = preSolution
31+
HideSolutionNode = FALSE
32+
EndGlobalSection
33+
GlobalSection(NestedProjects) = preSolution
34+
{0625DC02-DE9E-40FF-9D92-9C9F8B344EDC} = {C24B57E3-E01F-4109-A217-1773B633F907}
35+
EndGlobalSection
36+
GlobalSection(ExtensibilityGlobals) = postSolution
37+
SolutionGuid = {EF82FCCD-AF11-4236-96A5-7A3376715CBF}
38+
EndGlobalSection
39+
EndGlobal

fastchannel.png

3.23 KB
Loading
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Net;
4+
using System.Net.Http;
5+
using System.Net.Http.Headers;
6+
using System.Runtime.Serialization.Json;
7+
using System.Text;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
11+
namespace Kaive.HttpClient.OAuth2Handler.Authorizer
12+
{
13+
public class Authorizer : IAuthorizer
14+
{
15+
private readonly AuthorizerOptions _options;
16+
private readonly Func<System.Net.Http.HttpClient> _httpClientFactory;
17+
18+
// ReSharper disable once UnusedMember.Global
19+
public Authorizer(AuthorizerOptions options)
20+
: this(options, () => new System.Net.Http.HttpClient())
21+
{
22+
}
23+
24+
public Authorizer(AuthorizerOptions options, Func<System.Net.Http.HttpClient> httpClientFactory)
25+
{
26+
_options = options ?? throw new ArgumentNullException(nameof(options));
27+
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
28+
}
29+
30+
public async Task<TokenResponse> GetTokenAsync(CancellationToken? cancellationToken = null)
31+
{
32+
cancellationToken = cancellationToken ?? new CancellationToken(false);
33+
switch (_options.GrantType)
34+
{
35+
case GrantType.ClientCredentials:
36+
return await GetTokenWithClientCredentials(cancellationToken.Value);
37+
case GrantType.ResourceOwnerPasswordCredentials:
38+
return await GetTokenWithResourceOwnerPasswordCredentials(cancellationToken.Value);
39+
default:
40+
throw new NotSupportedException($"Requested grant type '{_options.GrantType}' is not supported.");
41+
}
42+
}
43+
44+
private Task<TokenResponse> GetTokenWithClientCredentials(CancellationToken cancellationToken)
45+
{
46+
if (_options.TokenEndpointUri == null) throw new ArgumentException("TokenEndpointUrl option cannot be null.");
47+
if (!_options.TokenEndpointUri.IsAbsoluteUri) throw new ArgumentException("TokenEndpointUrl must be an absolute Url.");
48+
49+
var properties = new Dictionary<string, string>
50+
{
51+
{ "grant_type", "client_credentials" }
52+
};
53+
54+
return GetTokenAsync(properties, cancellationToken);
55+
}
56+
57+
private Task<TokenResponse> GetTokenWithResourceOwnerPasswordCredentials(CancellationToken cancellationToken)
58+
{
59+
if (_options.TokenEndpointUri == null) throw new ArgumentException("TokenEndpointUrl option cannot be null.");
60+
if (!_options.TokenEndpointUri.IsAbsoluteUri) throw new ArgumentException("TokenEndpointUrl must be an absolute Url.");
61+
62+
if (_options.Username == null) throw new ArgumentException("Username cannot be null.");
63+
if (_options.Password == null) throw new ArgumentException("Password cannot be null.");
64+
65+
var properties = new Dictionary<string, string>
66+
{
67+
{ "grant_type", "password" },
68+
{ "username", _options.Username },
69+
{ "password", _options.Password }
70+
};
71+
72+
return GetTokenAsync(properties, cancellationToken);
73+
}
74+
75+
private async Task<TokenResponse> GetTokenAsync(IDictionary<string, string> properties, CancellationToken cancellationToken)
76+
{
77+
using (var client = _httpClientFactory())
78+
{
79+
if (_options.CredentialsTransportMethod == CredentialsTransportMethod.BasicAuthenticationCredentials)
80+
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", GetBasicAuthorizationHeaderValue());
81+
else if (_options.CredentialsTransportMethod == CredentialsTransportMethod.FormAuthenticationCredentials)
82+
{
83+
properties.Add("client_id", _options.ClientId);
84+
properties.Add("client_secret", _options.ClientSecret);
85+
}
86+
87+
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
88+
89+
if (_options.Scope != null)
90+
properties.Add("scope", string.Join(" ", _options.Scope));
91+
92+
if (_options.Resource != null)
93+
properties.Add("resource", _options.Resource);
94+
95+
var tokenUri = _options.TokenEndpointUri;
96+
if (_options.SetGrantTypeOnQueryString)
97+
tokenUri = new UriBuilder(tokenUri) {Query = "grant_type=" + properties["grant_type"]}.Uri;
98+
99+
var response = await client.PostAsync(tokenUri, new FormUrlEncodedContent(properties), cancellationToken);
100+
if (cancellationToken.IsCancellationRequested)
101+
return null;
102+
103+
if (!response.IsSuccessStatusCode)
104+
{
105+
RaiseProtocolException(response.StatusCode, await response.Content.ReadAsStringAsync());
106+
return null;
107+
}
108+
109+
var serializer = new DataContractJsonSerializer(typeof(TokenResponse));
110+
return serializer.ReadObject(await response.Content.ReadAsStreamAsync()) as TokenResponse;
111+
}
112+
}
113+
114+
private string GetBasicAuthorizationHeaderValue()
115+
{
116+
if (_options.ClientId == null) throw new ArgumentException("ClientId cannot be null.");
117+
if (_options.ClientSecret == null) throw new ArgumentException("ClientSecret cannot be null.");
118+
return Convert.ToBase64String(Encoding.UTF8.GetBytes($"{_options.ClientId}:{_options.ClientSecret}"));
119+
}
120+
121+
private void RaiseProtocolException(HttpStatusCode statusCode, string message)
122+
{
123+
if (_options.OnError != null)
124+
_options.OnError(statusCode, message);
125+
else
126+
throw new OAuthException(statusCode, message);
127+
}
128+
}
129+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Net;
4+
5+
namespace Kaive.HttpClient.OAuth2Handler.Authorizer
6+
{
7+
public class AuthorizerOptions
8+
{
9+
public Uri TokenEndpointUri { get; set; }
10+
11+
public string ClientId { get; set; }
12+
13+
public string ClientSecret { get; set; }
14+
15+
public string Username { get; set; }
16+
17+
public string Password { get; set; }
18+
19+
public string Resource { get; set; }
20+
21+
public IEnumerable<string> Scope { get; set; }
22+
23+
public bool SetGrantTypeOnQueryString { get; set; }
24+
25+
public GrantType GrantType { get; set; }
26+
27+
public CredentialsTransportMethod CredentialsTransportMethod { get; set; }
28+
29+
public Action<HttpStatusCode, string> OnError { get; set; }
30+
31+
public AuthorizerOptions()
32+
{
33+
CredentialsTransportMethod = CredentialsTransportMethod.BasicAuthenticationCredentials;
34+
}
35+
}
36+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Threading;
2+
using System.Threading.Tasks;
3+
4+
namespace Kaive.HttpClient.OAuth2Handler.Authorizer
5+
{
6+
public interface IAuthorizer
7+
{
8+
Task<TokenResponse> GetTokenAsync(CancellationToken? cancellationToken = null);
9+
}
10+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System;
2+
using System.Runtime.Serialization;
3+
4+
namespace Kaive.HttpClient.OAuth2Handler.Authorizer
5+
{
6+
[DataContract]
7+
public class TokenResponse
8+
{
9+
[DataMember(Name = "access_token")]
10+
public string AccessToken { get; set; }
11+
12+
[DataMember(Name = "refresh_token")]
13+
public string RefreshToken { get; set; }
14+
15+
[DataMember(Name = "token_type")]
16+
public string TokenType { get; set; }
17+
18+
[DataMember(Name = "expires_in")]
19+
public int ExpiresInSeconds { private get; set; }
20+
21+
public TimeSpan ExpiresIn => TimeSpan.FromSeconds(ExpiresInSeconds);
22+
}
23+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace Kaive.HttpClient.OAuth2Handler
2+
{
3+
public enum CredentialsTransportMethod
4+
{
5+
BasicAuthenticationCredentials,
6+
FormAuthenticationCredentials
7+
}
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace Kaive.HttpClient.OAuth2Handler
2+
{
3+
public enum GrantType
4+
{
5+
ClientCredentials,
6+
ResourceOwnerPasswordCredentials,
7+
}
8+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>netstandard2.0;net452</TargetFrameworks>
5+
</PropertyGroup>
6+
7+
<PropertyGroup>
8+
<Authors>FastChannel</Authors>
9+
<Description>Provides a generic OAuth2 handler to be used within System.Net.Http.HttpClient instances, enabling OAuth2 authorization for any requests made through the aforementionet HTTP client. Inspired on: https://github.com/huysentruitw/oauth2-client-handler</Description>
10+
<PackageLicenseUrl>https://raw.githubusercontent.com/fastchannel/kaive-httpclient-oauth-handler/master/LICENSE</PackageLicenseUrl>
11+
<PackageProjectUrl>https://github.com/fastchannel/kaive-httpclient-oauth-handler</PackageProjectUrl>
12+
<RepositoryUrl>https://github.com/fastchannel/kaive-httpclient-oauth-handler</RepositoryUrl>
13+
<PackageIconUrl>https://raw.githubusercontent.com/fastchannel/kaive-httpclient-oauth-handler/master/fastchannel.png</PackageIconUrl>
14+
<RepositoryType>GitHub</RepositoryType>
15+
<PackageTags>HttpClient HttpHandler OAuth2</PackageTags>
16+
<PackageReleaseNotes>Initial rush release, still missing unit tests and documentation.</PackageReleaseNotes>
17+
</PropertyGroup>
18+
19+
<ItemGroup Condition="'$(TargetFramework)' == 'net452'">
20+
<Reference Include="System.Net.Http" />
21+
</ItemGroup>
22+
</Project>

0 commit comments

Comments
 (0)