diff options
8 files changed, 85 insertions, 49 deletions
diff --git a/projecttemplates/RelyingPartyLogic/OAuthAuthorizationServer.cs b/projecttemplates/RelyingPartyLogic/OAuthAuthorizationServer.cs index ee727f5..8c3f6fd 100644 --- a/projecttemplates/RelyingPartyLogic/OAuthAuthorizationServer.cs +++ b/projecttemplates/RelyingPartyLogic/OAuthAuthorizationServer.cs @@ -59,11 +59,12 @@ namespace RelyingPartyLogic { /// Creates the access token encryption key. /// </summary> /// <param name="request">The request.</param> - public RSACryptoServiceProvider CreateAccessTokenEncryptionKey(IAccessTokenRequest request) { + public void PrepareAccessToken(IAccessTokenRequest accessTokenRequestMessage, out RSACryptoServiceProvider resourceServerEncryptionKey, out TimeSpan lifetime) { // For this sample, we assume just one resource server. // If this authorization server needs to mint access tokens for more than one resource server, // we'd look at the request message passed to us and decide which public key to return. - return OAuthResourceServer.CreateRSA(); + resourceServerEncryptionKey = OAuthResourceServer.CreateRSA(); + lifetime = TimeSpan.FromHours(1); } /// <summary> @@ -132,7 +133,7 @@ namespace RelyingPartyLogic { // Default to not auto-approving. return false; } - + private bool IsAuthorizationValid(HashSet<string> requestedScopes, string clientIdentifier, DateTime issuedUtc, string username) { var grantedScopeStrings = from auth in Database.DataContext.ClientAuthorizations where @@ -140,7 +141,7 @@ namespace RelyingPartyLogic { auth.CreatedOnUtc <= issuedUtc && (!auth.ExpirationDateUtc.HasValue || auth.ExpirationDateUtc.Value >= DateTime.UtcNow) && auth.User.AuthenticationTokens.Any(token => token.ClaimedIdentifier == username) - select auth.Scope; + select auth.Scope; if (!grantedScopeStrings.Any()) { // No granted authorizations prior to the issuance of this token, so it must have been revoked. diff --git a/samples/OAuthAuthorizationServer/Code/OAuth2AuthorizationServer.cs b/samples/OAuthAuthorizationServer/Code/OAuth2AuthorizationServer.cs index 07cdd9d..9e490b0 100644 --- a/samples/OAuthAuthorizationServer/Code/OAuth2AuthorizationServer.cs +++ b/samples/OAuthAuthorizationServer/Code/OAuth2AuthorizationServer.cs @@ -45,14 +45,23 @@ get { return AsymmetricTokenSigningPrivateKey; } } - public RSACryptoServiceProvider CreateAccessTokenEncryptionKey(IAccessTokenRequest accessTokenRequestMessage) { - var asymmetricTokenSigningServiceProvider = new RSACryptoServiceProvider(); + public void PrepareAccessToken(IAccessTokenRequest accessTokenRequestMessage, out RSACryptoServiceProvider resourceServerEncryptionKey, out TimeSpan lifetime) { + resourceServerEncryptionKey = new RSACryptoServiceProvider(); // For this sample, we assume just one resource server. // If this authorization server needs to mint access tokens for more than one resource server, // we'd look at the request message passed to us and decide which public key to return. - asymmetricTokenSigningServiceProvider.ImportParameters(ResourceServerEncryptionPublicKey); - return asymmetricTokenSigningServiceProvider; + resourceServerEncryptionKey.ImportParameters(ResourceServerEncryptionPublicKey); + + // Just for the sake of the sample, we use a short-lived token. This can be useful to mitigate the security risks + // of access tokens that are used over standard HTTP. + // But this is just the lifetime of the access token. The client can still renew it using their refresh token until + // the authorization itself expires. + lifetime = TimeSpan.FromMinutes(2); + + // Also take into account the remaining life of the authorization and artificially shorten the access token's lifetime + // to account for that if necessary. + // TODO: code here } public IConsumerDescription GetClient(string clientIdentifier) { @@ -77,7 +86,7 @@ } // NEVER issue an auto-approval to a client that would end up getting an access token immediately - // (without a client secret), as that would allow ANY client to spoof an approved client's identity + // (without a client secret), as that would allow arbitrary clients to masquarade as an approved client // and obtain unauthorized access to user data. if (authorizationRequest.ResponseType == EndUserAuthorizationResponseType.AuthorizationCode) { // Never issue auto-approval if the client secret is blank, since that too makes it easy to spoof diff --git a/samples/OAuthAuthorizationServer/Controllers/OAuthController.cs b/samples/OAuthAuthorizationServer/Controllers/OAuthController.cs index 07dc8cc..a67c57b 100644 --- a/samples/OAuthAuthorizationServer/Controllers/OAuthController.cs +++ b/samples/OAuthAuthorizationServer/Controllers/OAuthController.cs @@ -23,18 +23,8 @@ public ActionResult Token() {
var request = this.authorizationServer.ReadAccessTokenRequest();
if (request != null) {
- // Just for the sake of the sample, we use a short-lived token. This can be useful to mitigate the security risks
- // of access tokens that are used over standard HTTP.
- // But this is just the lifetime of the access token. The client can still renew it using their refresh token until
- // the authorization itself expires.
- TimeSpan accessTokenLifetime = TimeSpan.FromMinutes(2);
-
- // Also take into account the remaining life of the authorization and artificially shorten the access token's lifetime
- // to account for that if necessary.
- // TODO: code here
-
// Prepare the refresh and access tokens.
- var response = this.authorizationServer.PrepareAccessTokenResponse(request, accessTokenLifetime);
+ var response = this.authorizationServer.PrepareAccessTokenResponse(request);
return this.authorizationServer.Channel.PrepareResponse(response).AsActionResult();
}
diff --git a/src/DotNetOpenAuth/OAuth2/AuthorizationServer.cs b/src/DotNetOpenAuth/OAuth2/AuthorizationServer.cs index 0f57939..69768bc 100644 --- a/src/DotNetOpenAuth/OAuth2/AuthorizationServer.cs +++ b/src/DotNetOpenAuth/OAuth2/AuthorizationServer.cs @@ -208,14 +208,16 @@ namespace DotNetOpenAuth.OAuth2 { /// Prepares the response to an access token request. /// </summary> /// <param name="request">The request for an access token.</param> - /// <param name="accessTokenLifetime">The access token's lifetime.</param> /// <param name="includeRefreshToken">If set to <c>true</c>, the response will include a long-lived refresh token.</param> /// <returns>The response message to send to the client.</returns> - public virtual IDirectResponseProtocolMessage PrepareAccessTokenResponse(AccessTokenRequestBase request, TimeSpan? accessTokenLifetime = null, bool includeRefreshToken = true) { + public virtual IDirectResponseProtocolMessage PrepareAccessTokenResponse(AccessTokenRequestBase request, bool includeRefreshToken = true) { Contract.Requires<ArgumentNullException>(request != null); var tokenRequest = (IAuthorizationCarryingRequest)request; - using (var resourceServerEncryptionKey = this.AuthorizationServerServices.CreateAccessTokenEncryptionKey(request)) { + RSACryptoServiceProvider resourceServerEncryptionKey; + TimeSpan accessTokenLifetime; + this.AuthorizationServerServices.PrepareAccessToken(request, out resourceServerEncryptionKey, out accessTokenLifetime); + try { var accessTokenFormatter = AccessToken.CreateFormatter(this.AuthorizationServerServices.AccessTokenSigningKey, resourceServerEncryptionKey); var accessToken = new AccessToken(tokenRequest.AuthorizationDescription, accessTokenLifetime); @@ -232,6 +234,9 @@ namespace DotNetOpenAuth.OAuth2 { } return response; + } finally { + IDisposable disposableKey = resourceServerEncryptionKey; + disposableKey.Dispose(); } } diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs index 9c9ebb4..129a277 100644 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs +++ b/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs @@ -13,6 +13,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OAuth2.Messages; + using System.Security.Cryptography; /// <summary> /// Decodes verification codes, refresh tokens and access tokens on incoming messages. @@ -66,18 +67,25 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { var request = (IAccessTokenRequest)responseWithOriginatingRequest.OriginatingRequest; // TODO: consider moving this AccessToken construction to its own binding element. - response.AuthorizationDescription = new AccessToken { - ClientIdentifier = request.ClientIdentifier, - UtcCreationDate = DateTime.UtcNow, - User = ((EndUserAuthorizationSuccessResponseBase)response).AuthorizingUsername, - }; - response.AuthorizationDescription.Scope.ResetContents(request.Scope); - - using (var resourceServerKey = this.AuthorizationServer.CreateAccessTokenEncryptionKey(request)) { + RSACryptoServiceProvider resourceServerKey; + TimeSpan lifetime; + this.AuthorizationServer.PrepareAccessToken(request, out resourceServerKey, out lifetime); + try { + response.AuthorizationDescription = new AccessToken { + ClientIdentifier = request.ClientIdentifier, + UtcCreationDate = DateTime.UtcNow, + User = ((EndUserAuthorizationSuccessResponseBase)response).AuthorizingUsername, + Lifetime = lifetime, + }; + response.AuthorizationDescription.Scope.ResetContents(request.Scope); + var tokenFormatter = AccessToken.CreateFormatter(this.AuthorizationServer.AccessTokenSigningKey, resourceServerKey); var token = (AccessToken)response.AuthorizationDescription; response.CodeOrToken = tokenFormatter.Serialize(token); break; + } finally { + IDisposable disposableKey = resourceServerKey; + disposableKey.Dispose(); } default: throw ErrorUtilities.ThrowInternal(string.Format(CultureInfo.CurrentCulture, "Unexpected outgoing code or token type: {0}", response.CodeOrTokenType)); diff --git a/src/DotNetOpenAuth/OAuth2/IAuthorizationServer.cs b/src/DotNetOpenAuth/OAuth2/IAuthorizationServer.cs index 6458433..457ce65 100644 --- a/src/DotNetOpenAuth/OAuth2/IAuthorizationServer.cs +++ b/src/DotNetOpenAuth/OAuth2/IAuthorizationServer.cs @@ -49,17 +49,28 @@ namespace DotNetOpenAuth.OAuth2 { RSACryptoServiceProvider AccessTokenSigningKey { get; } /// <summary> - /// Gets the crypto service provider with the asymmetric public key to use for encrypting access tokens for a specific resource server. + /// Obtains the encryption key and lifetime for an access token being created. /// </summary> - /// <param name="accessTokenRequestMessage">The access token request message.</param> - /// <returns> - /// A crypto service provider instance that contains the public key. - /// </returns> - /// <value>Must not be null.</value> + /// <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> + /// <param name="resourceServerEncryptionKey"> + /// Receives the crypto service provider with the asymmetric public key to use for encrypting access tokens for a specific resource server. + /// The caller is responsible to dispose of this value. + /// </param> + /// <param name="lifetime"> + /// Receives the lifetime for this access token. Note that within this lifetime, authorization <i>may</i> not be revokable. + /// Short lifetimes are recommended (i.e. one hour), particularly when the client is not authenticated or + /// the resources to which access is being granted are sensitive. + /// If <c>null</c>, a preconfigured default lifetime will be used. + /// </param> /// <remarks> /// The caller is responsible to dispose of the returned value. /// </remarks> - RSACryptoServiceProvider CreateAccessTokenEncryptionKey(IAccessTokenRequest accessTokenRequestMessage); + void PrepareAccessToken(IAccessTokenRequest accessTokenRequestMessage, out RSACryptoServiceProvider resourceServerEncryptionKey, out TimeSpan lifetime); /// <summary> /// Gets the client with a given identifier. @@ -142,18 +153,30 @@ namespace DotNetOpenAuth.OAuth2 { } /// <summary> - /// Gets the crypto service provider with the asymmetric private key to use for signing access tokens. + /// Obtains the encryption key and lifetime for an access token being created. /// </summary> - /// <returns> - /// A crypto service provider instance that contains the private key. - /// </returns> - /// <value>Must not be null, and must contain the private key.</value> + /// <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> + /// <param name="resourceServerEncryptionKey"> + /// Receives the crypto service provider with the asymmetric public key to use for encrypting access tokens for a specific resource server. + /// The caller is responsible to dispose of this value. + /// </param> + /// <param name="lifetime"> + /// Receives the lifetime for this access token. Note that within this lifetime, authorization <i>may</i> not be revokable. + /// Short lifetimes are recommended (i.e. one hour), particularly when the client is not authenticated or + /// the resources to which access is being granted are sensitive. + /// If <c>null</c>, a preconfigured default lifetime will be used. + /// </param> /// <remarks> - /// The public key in the private/public key pair will be used by the resource - /// servers to validate that the access token is minted by a trusted authorization server. + /// The caller is responsible to dispose of the returned value. /// </remarks> - RSACryptoServiceProvider IAuthorizationServer.CreateAccessTokenEncryptionKey(IAccessTokenRequest accessTokenRequestMessage) { - Contract.Ensures(Contract.Result<RSACryptoServiceProvider>() != null); + void IAuthorizationServer.PrepareAccessToken(IAccessTokenRequest accessTokenRequestMessage, out RSACryptoServiceProvider resourceServerEncryptionKey, out TimeSpan lifetime) { + Contract.Requires<ArgumentNullException>(accessTokenRequestMessage != null); + Contract.Ensures(Contract.ValueAtReturn<RSACryptoServiceProvider>(out resourceServerEncryptionKey) != null); throw new NotImplementedException(); } diff --git a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessAccessTokenResponse.cs b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessAccessTokenResponse.cs index 4a3c534..a752a04 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessAccessTokenResponse.cs +++ b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessAccessTokenResponse.cs @@ -45,7 +45,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { this.TokenType = Protocol.AccessTokenTypes.Bearer; } - #region ITokenCarryingRequest Members + #region IAuthorizationCarryingRequest Members /// <summary> /// Gets or sets the verification code or refresh/access token. diff --git a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponse.cs b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponse.cs index 37d4cc2..65965ef 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponse.cs +++ b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponse.cs @@ -40,7 +40,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { ((IMessageWithClientState)this).ClientState = request.ClientState; } - #region ITokenCarryingRequest Members + #region IAuthorizationCarryingRequest Members /// <summary> /// Gets or sets the verification code or refresh/access token. |