summaryrefslogtreecommitdiffstats
path: root/samples/OAuth2ProtectedWebApi/Code
diff options
context:
space:
mode:
Diffstat (limited to 'samples/OAuth2ProtectedWebApi/Code')
-rw-r--r--samples/OAuth2ProtectedWebApi/Code/AuthorizationServerHost.cs181
-rw-r--r--samples/OAuth2ProtectedWebApi/Code/BearerTokenHandler.cs30
-rw-r--r--samples/OAuth2ProtectedWebApi/Code/HttpHeaderAttribute.cs41
3 files changed, 252 insertions, 0 deletions
diff --git a/samples/OAuth2ProtectedWebApi/Code/AuthorizationServerHost.cs b/samples/OAuth2ProtectedWebApi/Code/AuthorizationServerHost.cs
new file mode 100644
index 0000000..843280b
--- /dev/null
+++ b/samples/OAuth2ProtectedWebApi/Code/AuthorizationServerHost.cs
@@ -0,0 +1,181 @@
+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 {
+ /// <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 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 {
+ // 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) {
+ // 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) {
+ // 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 AnyCallbackClient();
+ }
+
+ /// <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) {
+ // 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) {
+ // 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);
+ }
+ }
+
+ private class AnyCallbackClient : ClientDescription {
+ public override bool IsCallbackAllowed(Uri callback) {
+ return true;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/samples/OAuth2ProtectedWebApi/Code/BearerTokenHandler.cs b/samples/OAuth2ProtectedWebApi/Code/BearerTokenHandler.cs
new file mode 100644
index 0000000..23ec087
--- /dev/null
+++ b/samples/OAuth2ProtectedWebApi/Code/BearerTokenHandler.cs
@@ -0,0 +1,30 @@
+namespace OAuth2ProtectedWebApi.Code {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Net.Http;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using System.Web;
+
+ 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") {
+ var resourceServer = new ResourceServer(new StandardAccessTokenAnalyzer(AuthorizationServerHost.HardCodedCryptoKeyStore));
+ var principal = await resourceServer.GetPrincipalAsync(request, cancellationToken);
+ HttpContext.Current.User = principal;
+ Thread.CurrentPrincipal = principal;
+ }
+ }
+
+ return await base.SendAsync(request, cancellationToken);
+ }
+ }
+} \ No newline at end of file
diff --git a/samples/OAuth2ProtectedWebApi/Code/HttpHeaderAttribute.cs b/samples/OAuth2ProtectedWebApi/Code/HttpHeaderAttribute.cs
new file mode 100644
index 0000000..3bff848
--- /dev/null
+++ b/samples/OAuth2ProtectedWebApi/Code/HttpHeaderAttribute.cs
@@ -0,0 +1,41 @@
+namespace OAuth2ProtectedWebApi.Code {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Web;
+ using System.Web.Mvc;
+
+ /// <summary>
+ /// Represents an attribute that is used to add HTTP Headers to a Controller Action response.
+ /// </summary>
+ public class HttpHeaderAttribute : ActionFilterAttribute {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="HttpHeaderAttribute"/> class.
+ /// </summary>
+ /// <param name="name">The HTTP header name.</param>
+ /// <param name="value">The HTTP header value.</param>
+ public HttpHeaderAttribute(string name, string value) {
+ this.Name = name;
+ this.Value = value;
+ }
+
+ /// <summary>
+ /// Gets or sets the name of the HTTP Header.
+ /// </summary>
+ public string Name { get; set; }
+
+ /// <summary>
+ /// Gets or sets the value of the HTTP Header.
+ /// </summary>
+ public string Value { get; set; }
+
+ /// <summary>
+ /// Called by the MVC framework after the action result executes.
+ /// </summary>
+ /// <param name="filterContext">The filter context.</param>
+ public override void OnResultExecuted(ResultExecutedContext filterContext) {
+ filterContext.HttpContext.Response.AppendHeader(this.Name, this.Value);
+ base.OnResultExecuted(filterContext);
+ }
+ }
+} \ No newline at end of file