diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2013-03-02 21:22:38 -0800 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2013-03-02 21:22:38 -0800 |
commit | 36cadbb1b9bf9c6a9a97b3679f40ea155ce0a615 (patch) | |
tree | af2a6af1ce23217a2ae8190c7ad775bd540e3be8 /samples/OAuth2ProtectedWebApi | |
parent | 09651b96839ce22116a4047876bb5a43164c1102 (diff) | |
download | DotNetOpenAuth-36cadbb1b9bf9c6a9a97b3679f40ea155ce0a615.zip DotNetOpenAuth-36cadbb1b9bf9c6a9a97b3679f40ea155ce0a615.tar.gz DotNetOpenAuth-36cadbb1b9bf9c6a9a97b3679f40ea155ce0a615.tar.bz2 |
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.
Diffstat (limited to 'samples/OAuth2ProtectedWebApi')
5 files changed, 142 insertions, 71 deletions
diff --git a/samples/OAuth2ProtectedWebApi/Code/AuthorizationServerHost.cs b/samples/OAuth2ProtectedWebApi/Code/AuthorizationServerHost.cs index 3149923..eb7f3f3 100644 --- a/samples/OAuth2ProtectedWebApi/Code/AuthorizationServerHost.cs +++ b/samples/OAuth2ProtectedWebApi/Code/AuthorizationServerHost.cs @@ -1,50 +1,175 @@ -namespace OAuth2ProtectedWebApi { +namespace OAuth2ProtectedWebApi.Code { using System; using System.Collections.Generic; using System.Linq; using System.Web; + using System.Web.Security; using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OAuth2; using DotNetOpenAuth.OAuth2.ChannelElements; using DotNetOpenAuth.OAuth2.Messages; using OAuth2ProtectedWebApi.Code; + /// <summary> + /// Provides application-specific policy and persistence for OAuth 2.0 authorization servers. + /// </summary> public class AuthorizationServerHost : IAuthorizationServerHost { - private static ICryptoKeyStore cryptoKeyStore = MemoryCryptoKeyStore.Instance; + /// <summary> + /// Storage for the cryptographic keys used to protect authorization codes, refresh tokens and access tokens. + /// </summary> + /// <remarks> + /// A single, hard-coded symmetric key is hardly adequate. Applications that rely on decent security should + /// replace this implementation with one that actually stores and retrieves keys in some persistent store + /// (e.g. a database). DotNetOpenAuth will automatically take care of generating, rotating, and expiring keys + /// if you provide a real implementation of this interface. + /// TODO: Consider replacing use of <see cref="HardCodedKeyCryptoKeyStore"/> with a real persisted database table. + /// </remarks> + internal static readonly ICryptoKeyStore HardCodedCryptoKeyStore = new HardCodedKeyCryptoKeyStore("p7J1L24Qj4KGYUOrnfENF0XAhqn6rZc5dx4nxvI22Kg="); + /// <summary> + /// Gets the store for storing crypto keys used to symmetrically encrypt and sign authorization codes and refresh tokens. + /// </summary> + /// <remarks> + /// This store should be kept strictly confidential in the authorization server(s) + /// and NOT shared with the resource server. Anyone with these secrets can mint + /// tokens to essentially grant themselves access to anything they want. + /// </remarks> public ICryptoKeyStore CryptoKeyStore { - get { return cryptoKeyStore; } + get { return HardCodedCryptoKeyStore; } } + /// <summary> + /// Gets the authorization code nonce store to use to ensure that authorization codes can only be used once. + /// </summary> + /// <value> + /// The authorization code nonce store. + /// </value> public INonceStore NonceStore { get { - // Implementing a nonce store is a good idea as it mitigates replay attacks. + // TODO: Consider implementing a nonce store to mitigate replay attacks on authorization codes. return null; } } + /// <summary> + /// Acquires the access token and related parameters that go into the formulation of the token endpoint's response to a client. + /// </summary> + /// <param name="accessTokenRequestMessage">Details regarding the resources that the access token will grant access to, and the identity of the client + /// that will receive that access. + /// Based on this information the receiving resource server can be determined and the lifetime of the access + /// token can be set based on the sensitivity of the resources.</param> + /// <returns> + /// A non-null parameters instance that DotNetOpenAuth will dispose after it has been used. + /// </returns> public AccessTokenResult CreateAccessToken(IAccessTokenRequest accessTokenRequestMessage) { - var accessToken = new AuthorizationServerAccessToken(); - accessToken.Lifetime = TimeSpan.FromHours(1); - accessToken.SymmetricKeyStore = this.CryptoKeyStore; + // If your resource server and authorization server are different web apps, + // consider using asymmetric keys instead of symmetric ones by setting different + // properties on the access token below. + var accessToken = new AuthorizationServerAccessToken { + Lifetime = TimeSpan.FromHours(1), + SymmetricKeyStore = this.CryptoKeyStore, + }; var result = new AccessTokenResult(accessToken); return result; } + /// <summary> + /// Gets the client with a given identifier. + /// </summary> + /// <param name="clientIdentifier">The client identifier.</param> + /// <returns> + /// The client registration. Never null. + /// </returns> + /// <exception cref="ArgumentException">Thrown when no client with the given identifier is registered with this authorization server.</exception> public IClientDescription GetClient(string clientIdentifier) { - return new ClientDescription("b", new Uri("http://www.microsoft.com/en-us/default.aspx"), ClientType.Confidential); + // TODO: Consider adding a clients table in your database to track actual client accounts + // with authenticating secrets. + // For now, just allow all clients regardless of ID, and consider them "Public" clients. + return new ClientDescription(); } + /// <summary> + /// Determines whether a described authorization is (still) valid. + /// </summary> + /// <param name="authorization">The authorization.</param> + /// <returns> + /// <c>true</c> if the original authorization is still valid; otherwise, <c>false</c>. + /// </returns> + /// <remarks> + /// <para>When establishing that an authorization is still valid, + /// it's very important to only match on recorded authorizations that + /// meet these criteria:</para> + /// 1) The client identifier matches. + /// 2) The user account matches. + /// 3) The scope on the recorded authorization must include all scopes in the given authorization. + /// 4) The date the recorded authorization was issued must be <em>no later</em> that the date the given authorization was issued. + /// <para>One possible scenario is where the user authorized a client, later revoked authorization, + /// and even later reinstated authorization. This subsequent recorded authorization + /// would not satisfy requirement #4 in the above list. This is important because the revocation + /// the user went through should invalidate all previously issued tokens as a matter of + /// security in the event the user was revoking access in order to sever authorization on a stolen + /// account or piece of hardware in which the tokens were stored. </para> + /// </remarks> public bool IsAuthorizationValid(IAuthorizationDescription authorization) { + // If your application supports access revocation (highly recommended), + // this method should return false if the specified authorization is not + // discovered in your current authorizations table. + //// TODO: code here + return true; } + /// <summary> + /// Determines whether a given set of resource owner credentials is valid based on the authorization server's user database + /// and if so records an authorization entry such that subsequent calls to <see cref="IsAuthorizationValid" /> would + /// return <c>true</c>. + /// </summary> + /// <param name="userName">Username on the account.</param> + /// <param name="password">The user's password.</param> + /// <param name="accessRequest">The access request the credentials came with. + /// This may be useful if the authorization server wishes to apply some policy based on the client that is making the request.</param> + /// <returns> + /// A value that describes the result of the authorization check. + /// </returns> + /// <exception cref="System.NotSupportedException"></exception> public AutomatedUserAuthorizationCheckResponse CheckAuthorizeResourceOwnerCredentialGrant(string userName, string password, IAccessTokenRequest accessRequest) { - throw new NotSupportedException(); + // TODO: Consider only accepting resource owner credential grants from specific clients + // based on accessRequest.ClientIdentifier and accessRequest.ClientAuthenticated. + if (Membership.ValidateUser(userName, password)) { + // Add an entry to your authorization table to record that access was granted so that + // you can conditionally return true from IsAuthorizationValid when the row is discovered. + //// TODO: code here + + // Inform DotNetOpenAuth that it may proceed to issue an access token. + return new AutomatedUserAuthorizationCheckResponse(accessRequest, true, Membership.GetUser(userName).UserName); + } else { + return new AutomatedUserAuthorizationCheckResponse(accessRequest, false, null); + } } + /// <summary> + /// Determines whether an access token request given a client credential grant should be authorized + /// and if so records an authorization entry such that subsequent calls to <see cref="IsAuthorizationValid" /> would + /// return <c>true</c>. + /// </summary> + /// <param name="accessRequest">The access request the credentials came with. + /// This may be useful if the authorization server wishes to apply some policy based on the client that is making the request.</param> + /// <returns> + /// A value that describes the result of the authorization check. + /// </returns> + /// <exception cref="System.NotSupportedException"></exception> public AutomatedAuthorizationCheckResponse CheckAuthorizeClientCredentialsGrant(IAccessTokenRequest accessRequest) { - throw new NotSupportedException(); + // TODO: Consider implementing this if your application should support clients that access data that + // doesn't belong to specific people, or clients that have elevated privileges and can access other + // people's data. + if (accessRequest.ClientAuthenticated) { + // Before returning a positive response, be *very careful* to validate the requested access scope + // to make sure it is appropriate for the requesting client. + throw new NotSupportedException(); + } else { + // Only authenticated clients should be given access. + return new AutomatedAuthorizationCheckResponse(accessRequest, false); + } } } }
\ No newline at end of file diff --git a/samples/OAuth2ProtectedWebApi/Code/BearerTokenHandler.cs b/samples/OAuth2ProtectedWebApi/Code/BearerTokenHandler.cs index 04296b4..23ec087 100644 --- a/samples/OAuth2ProtectedWebApi/Code/BearerTokenHandler.cs +++ b/samples/OAuth2ProtectedWebApi/Code/BearerTokenHandler.cs @@ -9,12 +9,15 @@ using DotNetOpenAuth.OAuth2; + /// <summary> + /// An HTTP server message handler that detects OAuth 2 bearer tokens in the authorization header + /// and applies the appropriate principal to the request when found. + /// </summary> public class BearerTokenHandler : DelegatingHandler { protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { if (request.Headers.Authorization != null) { if (request.Headers.Authorization.Scheme == "Bearer") { - string bearer = request.Headers.Authorization.Parameter; - var resourceServer = new ResourceServer(new StandardAccessTokenAnalyzer(MemoryCryptoKeyStore.Instance)); + var resourceServer = new ResourceServer(new StandardAccessTokenAnalyzer(AuthorizationServerHost.HardCodedCryptoKeyStore)); var principal = await resourceServer.GetPrincipalAsync(request, cancellationToken); HttpContext.Current.User = principal; Thread.CurrentPrincipal = principal; diff --git a/samples/OAuth2ProtectedWebApi/Code/MemoryCryptoKeyStore.cs b/samples/OAuth2ProtectedWebApi/Code/MemoryCryptoKeyStore.cs deleted file mode 100644 index 2bed4fd..0000000 --- a/samples/OAuth2ProtectedWebApi/Code/MemoryCryptoKeyStore.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace OAuth2ProtectedWebApi.Code { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Web; - - using DotNetOpenAuth.Messaging.Bindings; - - internal class MemoryCryptoKeyStore : ICryptoKeyStore { - private Dictionary<string, Dictionary<string, CryptoKey>> keys = new Dictionary<string, Dictionary<string, CryptoKey>>(); - - private MemoryCryptoKeyStore() { - } - - internal static ICryptoKeyStore Instance = new MemoryCryptoKeyStore(); - - public CryptoKey GetKey(string bucket, string handle) { - Dictionary<string, CryptoKey> keyBucket; - if (this.keys.TryGetValue(bucket, out keyBucket)) { - CryptoKey key; - if (keyBucket.TryGetValue(handle, out key)) { - return key; - } - } - - return null; - } - - public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) { - Dictionary<string, CryptoKey> keyBucket; - if (this.keys.TryGetValue(bucket, out keyBucket)) { - foreach (var cryptoKey in keyBucket) { - yield return cryptoKey; - } - } - } - - public void StoreKey(string bucket, string handle, CryptoKey key) { - Dictionary<string, CryptoKey> keyBucket; - if (!this.keys.TryGetValue(bucket, out keyBucket)) { - keyBucket = this.keys[bucket] = new Dictionary<string, CryptoKey>(); - } - - keyBucket[handle] = key; - } - - public void RemoveKey(string bucket, string handle) { - Dictionary<string, CryptoKey> keyBucket; - if (this.keys.TryGetValue(bucket, out keyBucket)) { - keyBucket.Remove(handle); - } - } - } -}
\ No newline at end of file diff --git a/samples/OAuth2ProtectedWebApi/Controllers/TokenController.cs b/samples/OAuth2ProtectedWebApi/Controllers/TokenController.cs index 2e2873a..a6ecf32 100644 --- a/samples/OAuth2ProtectedWebApi/Controllers/TokenController.cs +++ b/samples/OAuth2ProtectedWebApi/Controllers/TokenController.cs @@ -1,14 +1,12 @@ namespace OAuth2ProtectedWebApi.Controllers { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Net; using System.Net.Http; using System.Threading.Tasks; using System.Web.Http; using DotNetOpenAuth.OAuth2; + using OAuth2ProtectedWebApi.Code; + public class TokenController : ApiController { // POST /api/token public Task<HttpResponseMessage> Post(HttpRequestMessage request) { diff --git a/samples/OAuth2ProtectedWebApi/OAuth2ProtectedWebApi.csproj b/samples/OAuth2ProtectedWebApi/OAuth2ProtectedWebApi.csproj index 321d808..380e62b 100644 --- a/samples/OAuth2ProtectedWebApi/OAuth2ProtectedWebApi.csproj +++ b/samples/OAuth2ProtectedWebApi/OAuth2ProtectedWebApi.csproj @@ -128,7 +128,6 @@ <Compile Include="Code\AuthorizationServerHost.cs" /> <Compile Include="Code\BearerTokenHandler.cs" /> <Compile Include="Code\HttpHeaderAttribute.cs" /> - <Compile Include="Code\MemoryCryptoKeyStore.cs" /> <Compile Include="Controllers\HomeController.cs" /> <Compile Include="Controllers\TokenController.cs" /> <Compile Include="Controllers\UserController.cs" /> |