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; /// /// Provides application-specific policy and persistence for OAuth 2.0 authorization servers. /// public class AuthorizationServerHost : IAuthorizationServerHost { /// /// Storage for the cryptographic keys used to protect authorization codes, refresh tokens and access tokens. /// /// /// 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 with a real persisted database table. /// internal static readonly ICryptoKeyStore HardCodedCryptoKeyStore = new HardCodedKeyCryptoKeyStore("p7J1L24Qj4KGYUOrnfENF0XAhqn6rZc5dx4nxvI22Kg="); /// /// Gets the store for storing crypto keys used to symmetrically encrypt and sign authorization codes and refresh tokens. /// /// /// 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. /// public ICryptoKeyStore CryptoKeyStore { get { return HardCodedCryptoKeyStore; } } /// /// Gets the authorization code nonce store to use to ensure that authorization codes can only be used once. /// /// /// The authorization code nonce store. /// public INonceStore NonceStore { get { // TODO: Consider implementing a nonce store to mitigate replay attacks on authorization codes. return null; } } /// /// Acquires the access token and related parameters that go into the formulation of the token endpoint's response to a client. /// /// 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. /// /// A non-null parameters instance that DotNetOpenAuth will dispose after it has been used. /// 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; } /// /// Gets the client with a given identifier. /// /// The client identifier. /// /// The client registration. Never null. /// /// Thrown when no client with the given identifier is registered with this authorization server. 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(); } /// /// Determines whether a described authorization is (still) valid. /// /// The authorization. /// /// true if the original authorization is still valid; otherwise, false. /// /// /// When establishing that an authorization is still valid, /// it's very important to only match on recorded authorizations that /// meet these criteria: /// 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 no later that the date the given authorization was issued. /// 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. /// 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; } /// /// 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 would /// return true. /// /// Username on the account. /// The user's password. /// 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. /// /// A value that describes the result of the authorization check. /// /// 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); } } /// /// 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 would /// return true. /// /// 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. /// /// A value that describes the result of the authorization check. /// /// 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; } } } }