Skip to content
This repository was archived by the owner on Mar 20, 2019. It is now read-only.

Commit 36cadbb

Browse files
committed
Removes the memory crypto key store from the sample.
We now have a 'hard-coded' secret key store that trivial apps/samples may use to keep things simple until they create a database table.
1 parent 09651b9 commit 36cadbb

8 files changed

Lines changed: 255 additions & 73 deletions

File tree

Lines changed: 135 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,175 @@
1-
namespace OAuth2ProtectedWebApi {
1+
namespace OAuth2ProtectedWebApi.Code {
22
using System;
33
using System.Collections.Generic;
44
using System.Linq;
55
using System.Web;
6+
using System.Web.Security;
67
using DotNetOpenAuth.Messaging.Bindings;
78
using DotNetOpenAuth.OAuth2;
89
using DotNetOpenAuth.OAuth2.ChannelElements;
910
using DotNetOpenAuth.OAuth2.Messages;
1011
using OAuth2ProtectedWebApi.Code;
1112

13+
/// <summary>
14+
/// Provides application-specific policy and persistence for OAuth 2.0 authorization servers.
15+
/// </summary>
1216
public class AuthorizationServerHost : IAuthorizationServerHost {
13-
private static ICryptoKeyStore cryptoKeyStore = MemoryCryptoKeyStore.Instance;
17+
/// <summary>
18+
/// Storage for the cryptographic keys used to protect authorization codes, refresh tokens and access tokens.
19+
/// </summary>
20+
/// <remarks>
21+
/// A single, hard-coded symmetric key is hardly adequate. Applications that rely on decent security should
22+
/// replace this implementation with one that actually stores and retrieves keys in some persistent store
23+
/// (e.g. a database). DotNetOpenAuth will automatically take care of generating, rotating, and expiring keys
24+
/// if you provide a real implementation of this interface.
25+
/// TODO: Consider replacing use of <see cref="HardCodedKeyCryptoKeyStore"/> with a real persisted database table.
26+
/// </remarks>
27+
internal static readonly ICryptoKeyStore HardCodedCryptoKeyStore = new HardCodedKeyCryptoKeyStore("p7J1L24Qj4KGYUOrnfENF0XAhqn6rZc5dx4nxvI22Kg=");
1428

29+
/// <summary>
30+
/// Gets the store for storing crypto keys used to symmetrically encrypt and sign authorization codes and refresh tokens.
31+
/// </summary>
32+
/// <remarks>
33+
/// This store should be kept strictly confidential in the authorization server(s)
34+
/// and NOT shared with the resource server. Anyone with these secrets can mint
35+
/// tokens to essentially grant themselves access to anything they want.
36+
/// </remarks>
1537
public ICryptoKeyStore CryptoKeyStore {
16-
get { return cryptoKeyStore; }
38+
get { return HardCodedCryptoKeyStore; }
1739
}
1840

41+
/// <summary>
42+
/// Gets the authorization code nonce store to use to ensure that authorization codes can only be used once.
43+
/// </summary>
44+
/// <value>
45+
/// The authorization code nonce store.
46+
/// </value>
1947
public INonceStore NonceStore {
2048
get {
21-
// Implementing a nonce store is a good idea as it mitigates replay attacks.
49+
// TODO: Consider implementing a nonce store to mitigate replay attacks on authorization codes.
2250
return null;
2351
}
2452
}
2553

54+
/// <summary>
55+
/// Acquires the access token and related parameters that go into the formulation of the token endpoint's response to a client.
56+
/// </summary>
57+
/// <param name="accessTokenRequestMessage">Details regarding the resources that the access token will grant access to, and the identity of the client
58+
/// that will receive that access.
59+
/// Based on this information the receiving resource server can be determined and the lifetime of the access
60+
/// token can be set based on the sensitivity of the resources.</param>
61+
/// <returns>
62+
/// A non-null parameters instance that DotNetOpenAuth will dispose after it has been used.
63+
/// </returns>
2664
public AccessTokenResult CreateAccessToken(IAccessTokenRequest accessTokenRequestMessage) {
27-
var accessToken = new AuthorizationServerAccessToken();
28-
accessToken.Lifetime = TimeSpan.FromHours(1);
29-
accessToken.SymmetricKeyStore = this.CryptoKeyStore;
65+
// If your resource server and authorization server are different web apps,
66+
// consider using asymmetric keys instead of symmetric ones by setting different
67+
// properties on the access token below.
68+
var accessToken = new AuthorizationServerAccessToken {
69+
Lifetime = TimeSpan.FromHours(1),
70+
SymmetricKeyStore = this.CryptoKeyStore,
71+
};
3072
var result = new AccessTokenResult(accessToken);
3173
return result;
3274
}
3375

76+
/// <summary>
77+
/// Gets the client with a given identifier.
78+
/// </summary>
79+
/// <param name="clientIdentifier">The client identifier.</param>
80+
/// <returns>
81+
/// The client registration. Never null.
82+
/// </returns>
83+
/// <exception cref="ArgumentException">Thrown when no client with the given identifier is registered with this authorization server.</exception>
3484
public IClientDescription GetClient(string clientIdentifier) {
35-
return new ClientDescription("b", new Uri("http://www.microsoft.com/en-us/default.aspx"), ClientType.Confidential);
85+
// TODO: Consider adding a clients table in your database to track actual client accounts
86+
// with authenticating secrets.
87+
// For now, just allow all clients regardless of ID, and consider them "Public" clients.
88+
return new ClientDescription();
3689
}
3790

91+
/// <summary>
92+
/// Determines whether a described authorization is (still) valid.
93+
/// </summary>
94+
/// <param name="authorization">The authorization.</param>
95+
/// <returns>
96+
/// <c>true</c> if the original authorization is still valid; otherwise, <c>false</c>.
97+
/// </returns>
98+
/// <remarks>
99+
/// <para>When establishing that an authorization is still valid,
100+
/// it's very important to only match on recorded authorizations that
101+
/// meet these criteria:</para>
102+
/// 1) The client identifier matches.
103+
/// 2) The user account matches.
104+
/// 3) The scope on the recorded authorization must include all scopes in the given authorization.
105+
/// 4) The date the recorded authorization was issued must be <em>no later</em> that the date the given authorization was issued.
106+
/// <para>One possible scenario is where the user authorized a client, later revoked authorization,
107+
/// and even later reinstated authorization. This subsequent recorded authorization
108+
/// would not satisfy requirement #4 in the above list. This is important because the revocation
109+
/// the user went through should invalidate all previously issued tokens as a matter of
110+
/// security in the event the user was revoking access in order to sever authorization on a stolen
111+
/// account or piece of hardware in which the tokens were stored. </para>
112+
/// </remarks>
38113
public bool IsAuthorizationValid(IAuthorizationDescription authorization) {
114+
// If your application supports access revocation (highly recommended),
115+
// this method should return false if the specified authorization is not
116+
// discovered in your current authorizations table.
117+
//// TODO: code here
118+
39119
return true;
40120
}
41121

122+
/// <summary>
123+
/// Determines whether a given set of resource owner credentials is valid based on the authorization server's user database
124+
/// and if so records an authorization entry such that subsequent calls to <see cref="IsAuthorizationValid" /> would
125+
/// return <c>true</c>.
126+
/// </summary>
127+
/// <param name="userName">Username on the account.</param>
128+
/// <param name="password">The user's password.</param>
129+
/// <param name="accessRequest">The access request the credentials came with.
130+
/// This may be useful if the authorization server wishes to apply some policy based on the client that is making the request.</param>
131+
/// <returns>
132+
/// A value that describes the result of the authorization check.
133+
/// </returns>
134+
/// <exception cref="System.NotSupportedException"></exception>
42135
public AutomatedUserAuthorizationCheckResponse CheckAuthorizeResourceOwnerCredentialGrant(string userName, string password, IAccessTokenRequest accessRequest) {
43-
throw new NotSupportedException();
136+
// TODO: Consider only accepting resource owner credential grants from specific clients
137+
// based on accessRequest.ClientIdentifier and accessRequest.ClientAuthenticated.
138+
if (Membership.ValidateUser(userName, password)) {
139+
// Add an entry to your authorization table to record that access was granted so that
140+
// you can conditionally return true from IsAuthorizationValid when the row is discovered.
141+
//// TODO: code here
142+
143+
// Inform DotNetOpenAuth that it may proceed to issue an access token.
144+
return new AutomatedUserAuthorizationCheckResponse(accessRequest, true, Membership.GetUser(userName).UserName);
145+
} else {
146+
return new AutomatedUserAuthorizationCheckResponse(accessRequest, false, null);
147+
}
44148
}
45149

150+
/// <summary>
151+
/// Determines whether an access token request given a client credential grant should be authorized
152+
/// and if so records an authorization entry such that subsequent calls to <see cref="IsAuthorizationValid" /> would
153+
/// return <c>true</c>.
154+
/// </summary>
155+
/// <param name="accessRequest">The access request the credentials came with.
156+
/// This may be useful if the authorization server wishes to apply some policy based on the client that is making the request.</param>
157+
/// <returns>
158+
/// A value that describes the result of the authorization check.
159+
/// </returns>
160+
/// <exception cref="System.NotSupportedException"></exception>
46161
public AutomatedAuthorizationCheckResponse CheckAuthorizeClientCredentialsGrant(IAccessTokenRequest accessRequest) {
47-
throw new NotSupportedException();
162+
// TODO: Consider implementing this if your application should support clients that access data that
163+
// doesn't belong to specific people, or clients that have elevated privileges and can access other
164+
// people's data.
165+
if (accessRequest.ClientAuthenticated) {
166+
// Before returning a positive response, be *very careful* to validate the requested access scope
167+
// to make sure it is appropriate for the requesting client.
168+
throw new NotSupportedException();
169+
} else {
170+
// Only authenticated clients should be given access.
171+
return new AutomatedAuthorizationCheckResponse(accessRequest, false);
172+
}
48173
}
49174
}
50175
}

samples/OAuth2ProtectedWebApi/Code/BearerTokenHandler.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@
99

1010
using DotNetOpenAuth.OAuth2;
1111

12+
/// <summary>
13+
/// An HTTP server message handler that detects OAuth 2 bearer tokens in the authorization header
14+
/// and applies the appropriate principal to the request when found.
15+
/// </summary>
1216
public class BearerTokenHandler : DelegatingHandler {
1317
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
1418
if (request.Headers.Authorization != null) {
1519
if (request.Headers.Authorization.Scheme == "Bearer") {
16-
string bearer = request.Headers.Authorization.Parameter;
17-
var resourceServer = new ResourceServer(new StandardAccessTokenAnalyzer(MemoryCryptoKeyStore.Instance));
20+
var resourceServer = new ResourceServer(new StandardAccessTokenAnalyzer(AuthorizationServerHost.HardCodedCryptoKeyStore));
1821
var principal = await resourceServer.GetPrincipalAsync(request, cancellationToken);
1922
HttpContext.Current.User = principal;
2023
Thread.CurrentPrincipal = principal;

samples/OAuth2ProtectedWebApi/Code/MemoryCryptoKeyStore.cs

Lines changed: 0 additions & 54 deletions
This file was deleted.

samples/OAuth2ProtectedWebApi/Controllers/TokenController.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
namespace OAuth2ProtectedWebApi.Controllers {
2-
using System;
3-
using System.Collections.Generic;
4-
using System.Linq;
5-
using System.Net;
62
using System.Net.Http;
73
using System.Threading.Tasks;
84
using System.Web.Http;
95

106
using DotNetOpenAuth.OAuth2;
117

8+
using OAuth2ProtectedWebApi.Code;
9+
1210
public class TokenController : ApiController {
1311
// POST /api/token
1412
public Task<HttpResponseMessage> Post(HttpRequestMessage request) {

samples/OAuth2ProtectedWebApi/OAuth2ProtectedWebApi.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,6 @@
128128
<Compile Include="Code\AuthorizationServerHost.cs" />
129129
<Compile Include="Code\BearerTokenHandler.cs" />
130130
<Compile Include="Code\HttpHeaderAttribute.cs" />
131-
<Compile Include="Code\MemoryCryptoKeyStore.cs" />
132131
<Compile Include="Controllers\HomeController.cs" />
133132
<Compile Include="Controllers\TokenController.cs" />
134133
<Compile Include="Controllers\UserController.cs" />

src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<Compile Include="Messaging\Bindings\AsymmetricCryptoKeyStoreWrapper.cs" />
2828
<Compile Include="Messaging\Bindings\CryptoKey.cs" />
2929
<Compile Include="Messaging\Bindings\CryptoKeyCollisionException.cs" />
30+
<Compile Include="Messaging\Bindings\HardCodedKeyCryptoKeyStore.cs" />
3031
<Compile Include="Messaging\Bindings\ICryptoKeyStore.cs" />
3132
<Compile Include="Messaging\Bindings\MemoryCryptoKeyStore.cs" />
3233
<Compile Include="Messaging\BinaryDataBagFormatter.cs" />
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="HardCodedKeyCryptoKeyStore.cs" company="Andrew Arnott">
3+
// Copyright (c) Andrew Arnott. All rights reserved.
4+
// </copyright>
5+
//-----------------------------------------------------------------------
6+
7+
namespace DotNetOpenAuth.Messaging.Bindings {
8+
using System;
9+
using System.Collections.Generic;
10+
using Validation;
11+
12+
/// <summary>
13+
/// A trivial implementation of <see cref="ICryptoKeyStore"/> that has only one fixed key.
14+
/// This is meant for simple, low-security applications. Greater security requires an
15+
/// implementation of <see cref="ICryptoKeyStore"/> that actually stores and retrieves
16+
/// keys from a persistent store.
17+
/// </summary>
18+
public class HardCodedKeyCryptoKeyStore : ICryptoKeyStore {
19+
/// <summary>
20+
/// The handle to report for the hard-coded key.
21+
/// </summary>
22+
private const string HardCodedKeyHandle = "fxd";
23+
24+
/// <summary>
25+
/// The one crypto key singleton instance.
26+
/// </summary>
27+
private readonly CryptoKey OneCryptoKey;
28+
29+
/// <summary>
30+
/// Initializes a new instance of the <see cref="HardCodedKeyCryptoKeyStore"/> class.
31+
/// </summary>
32+
/// <param name="secretAsBase64">The 256-bit secret as a base64 encoded string.</param>
33+
public HardCodedKeyCryptoKeyStore(string secretAsBase64)
34+
: this(Convert.FromBase64String(Requires.NotNull(secretAsBase64, "secretAsBase64"))) {
35+
}
36+
37+
/// <summary>
38+
/// Initializes a new instance of the <see cref="HardCodedKeyCryptoKeyStore"/> class.
39+
/// </summary>
40+
/// <param name="secret">The 256-bit secret.</param>
41+
public HardCodedKeyCryptoKeyStore(byte[] secret) {
42+
Requires.NotNull(secret, "secret");
43+
this.OneCryptoKey = new CryptoKey(secret, DateTime.MaxValue.AddDays(-2).ToUniversalTime());
44+
}
45+
46+
#region ICryptoKeyStore Members
47+
48+
/// <summary>
49+
/// Gets the key in a given bucket and handle.
50+
/// </summary>
51+
/// <param name="bucket">The bucket name. Case sensitive.</param>
52+
/// <param name="handle">The key handle. Case sensitive.</param>
53+
/// <returns>
54+
/// The cryptographic key, or <c>null</c> if no matching key was found.
55+
/// </returns>
56+
public CryptoKey GetKey(string bucket, string handle) {
57+
if (handle == HardCodedKeyHandle) {
58+
return OneCryptoKey;
59+
}
60+
61+
return null;
62+
}
63+
64+
/// <summary>
65+
/// Gets a sequence of existing keys within a given bucket.
66+
/// </summary>
67+
/// <param name="bucket">The bucket name. Case sensitive.</param>
68+
/// <returns>
69+
/// A sequence of handles and keys, ordered by descending <see cref="CryptoKey.ExpiresUtc" />.
70+
/// </returns>
71+
public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) {
72+
return new[] { new KeyValuePair<string, CryptoKey>(HardCodedKeyHandle, OneCryptoKey) };
73+
}
74+
75+
/// <summary>
76+
/// Stores a cryptographic key.
77+
/// </summary>
78+
/// <param name="bucket">The name of the bucket to store the key in. Case sensitive.</param>
79+
/// <param name="handle">The handle to the key, unique within the bucket. Case sensitive.</param>
80+
/// <param name="key">The key to store.</param>
81+
/// <exception cref="System.NotSupportedException"></exception>
82+
public void StoreKey(string bucket, string handle, CryptoKey key) {
83+
throw new NotSupportedException();
84+
}
85+
86+
/// <summary>
87+
/// Removes the key.
88+
/// </summary>
89+
/// <param name="bucket">The bucket name. Case sensitive.</param>
90+
/// <param name="handle">The key handle. Case sensitive.</param>
91+
/// <exception cref="System.NotSupportedException"></exception>
92+
public void RemoveKey(string bucket, string handle) {
93+
throw new NotSupportedException();
94+
}
95+
96+
#endregion
97+
}
98+
}

0 commit comments

Comments
 (0)