diff --git a/samples/Mvc.Client/Startup.cs b/samples/Mvc.Client/Startup.cs index 169ddd25..597b30d6 100644 --- a/samples/Mvc.Client/Startup.cs +++ b/samples/Mvc.Client/Startup.cs @@ -46,7 +46,7 @@ namespace Mvc.Client { // the different endpoints URIs or the token validation parameters explicitly. Authority = "http://localhost:54540/", - Scope = { "email", "roles" } + Scope = { "email", "roles", "offline_access" } }); app.UseMvc(); diff --git a/samples/Mvc.Server/Models/ApplicationDbContext.cs b/samples/Mvc.Server/Models/ApplicationDbContext.cs index 4127fc7c..e8b35b12 100644 --- a/samples/Mvc.Server/Models/ApplicationDbContext.cs +++ b/samples/Mvc.Server/Models/ApplicationDbContext.cs @@ -1,8 +1,10 @@ -using Microsoft.EntityFrameworkCore; +using System; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using OpenIddict; namespace Mvc.Server.Models { - public class ApplicationDbContext : OpenIddictContext { + public class ApplicationDbContext : OpenIddictContext, Guid> { public ApplicationDbContext(DbContextOptions options) : base(options) { } diff --git a/samples/Mvc.Server/Models/ApplicationUser.cs b/samples/Mvc.Server/Models/ApplicationUser.cs index 0a873641..c83df9f3 100644 --- a/samples/Mvc.Server/Models/ApplicationUser.cs +++ b/samples/Mvc.Server/Models/ApplicationUser.cs @@ -1,6 +1,7 @@ -using OpenIddict; +using System; +using OpenIddict; namespace Mvc.Server.Models { // Add profile data for application users by adding properties to the ApplicationUser class - public class ApplicationUser : OpenIddictUser { } + public class ApplicationUser : OpenIddictUser { } } diff --git a/samples/Mvc.Server/Startup.cs b/samples/Mvc.Server/Startup.cs index 9459f231..7483dead 100644 --- a/samples/Mvc.Server/Startup.cs +++ b/samples/Mvc.Server/Startup.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using AspNet.Security.OAuth.GitHub; using CryptoHelper; @@ -28,12 +29,12 @@ namespace Mvc.Server { options.UseSqlServer(configuration["Data:DefaultConnection:ConnectionString"])); // Register the Identity services. - services.AddIdentity() - .AddEntityFrameworkStores() + services.AddIdentity>() + .AddEntityFrameworkStores() .AddDefaultTokenProviders(); // Register the OpenIddict services, including the default Entity Framework stores. - services.AddOpenIddict() + services.AddOpenIddict, ApplicationDbContext, Guid>() // Register the HTML/CSS assets and MVC modules to handle the interactive flows. // Note: these modules are not necessary when using your own authorization controller // or when using non-interactive flows-only like the resource owner password credentials grant. @@ -146,12 +147,12 @@ namespace Mvc.Server { // Type = OpenIddictConstants.ClientTypes.Confidential // }); - context.Applications.Add(new OpenIddictApplication { - Id = "myClient", + context.Applications.Add(new OpenIddictApplication { + ClientId = "myClient", + ClientSecret = Crypto.HashPassword("secret_secret_secret"), DisplayName = "My client application", - RedirectUri = "http://localhost:53507/signin-oidc", LogoutRedirectUri = "http://localhost:53507/", - Secret = Crypto.HashPassword("secret_secret_secret"), + RedirectUri = "http://localhost:53507/signin-oidc", Type = OpenIddictConstants.ClientTypes.Confidential }); @@ -164,8 +165,8 @@ namespace Mvc.Server { // * Scope: openid email profile roles // * Grant type: authorization code // * Request access token locally: yes - context.Applications.Add(new OpenIddictApplication { - Id = "postman", + context.Applications.Add(new OpenIddictApplication { + ClientId = "postman", DisplayName = "Postman", RedirectUri = "https://www.getpostman.com/oauth2/callback", Type = OpenIddictConstants.ClientTypes.Public diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs index 365e5f34..e51474a9 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs @@ -36,7 +36,7 @@ namespace OpenIddict.Infrastructure { } // Retrieve the application details corresponding to the requested client_id. - var application = await services.Applications.FindByIdAsync(context.ClientId); + var application = await services.Applications.FindByClientIdAsync(context.ClientId); if (application == null) { services.Logger.LogError("The authorization request was rejected because the client " + "application was not found: '{ClientId}'.", context.ClientId); diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs index de4920f4..6272e40a 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs @@ -72,7 +72,7 @@ namespace OpenIddict.Infrastructure { } // Retrieve the application details corresponding to the requested client_id. - var application = await services.Applications.FindByIdAsync(context.ClientId); + var application = await services.Applications.FindByClientIdAsync(context.ClientId); if (application == null) { services.Logger.LogError("The token request was rejected because the client " + "application was not found: '{ClientId}'.", context.ClientId); @@ -132,7 +132,7 @@ namespace OpenIddict.Infrastructure { // Retrieve the application details corresponding to the requested client_id. // Note: this call shouldn't return a null instance, but a race condition may occur // if the application was removed after the initial check made by ValidateTokenRequest. - var application = await services.Applications.FindByIdAsync(context.ClientId); + var application = await services.Applications.FindByClientIdAsync(context.ClientId); if (application == null) { throw new InvalidOperationException("The token request was aborted because the client application corresponding " + $"to the '{context.ClientId}' identifier was not found in the database."); diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Introspection.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Introspection.cs index 2d23733d..ff75310b 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Introspection.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Introspection.cs @@ -42,7 +42,7 @@ namespace OpenIddict.Infrastructure { } // Retrieve the application details corresponding to the requested client_id. - var application = await services.Applications.FindByIdAsync(context.ClientId); + var application = await services.Applications.FindByClientIdAsync(context.ClientId); if (application == null) { services.Logger.LogError("The introspection request was rejected because the client " + "application was not found: '{ClientId}'.", context.ClientId); diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs index 6eb4c543..6bc00d09 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs @@ -45,7 +45,7 @@ namespace OpenIddict.Infrastructure { } // Retrieve the application details corresponding to the requested client_id. - var application = await services.Applications.FindByIdAsync(context.ClientId); + var application = await services.Applications.FindByClientIdAsync(context.ClientId); if (application == null) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs index ad0f877a..5b844603 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs @@ -40,7 +40,7 @@ namespace OpenIddict.Infrastructure { // If the client application sending the token request is known, // ensure the token is attached to the corresponding client entity. if (!string.IsNullOrEmpty(context.Request.ClientId)) { - var application = await services.Applications.FindByIdAsync(context.Request.ClientId); + var application = await services.Applications.FindByClientIdAsync(context.Request.ClientId); if (application == null) { throw new InvalidOperationException("The application cannot be retrieved from the database."); } diff --git a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs index 180c6855..3d61e09b 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs @@ -77,6 +77,18 @@ namespace OpenIddict { return Store.FindByIdAsync(identifier, CancellationToken); } + /// + /// Retrieves an application using its client identifier. + /// + /// The client identifier associated with the application. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client application corresponding to the identifier. + /// + public virtual Task FindByClientIdAsync(string identifier) { + return Store.FindByClientIdAsync(identifier, CancellationToken); + } + /// /// Retrieves an application using its post_logout_redirect_uri. /// diff --git a/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs index bb3fad7e..474cb743 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs +++ b/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs @@ -36,6 +36,17 @@ namespace OpenIddict { /// Task FindByIdAsync(string identifier, CancellationToken cancellationToken); + /// + /// Retrieves an application using its client identifier. + /// + /// The client identifier associated with the application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client application corresponding to the identifier. + /// + Task FindByClientIdAsync(string identifier, CancellationToken cancellationToken); + /// /// Retrieves an application using its post_logout_redirect_uri. /// diff --git a/src/OpenIddict.EntityFramework/Models/OpenIddictApplication.cs b/src/OpenIddict.EntityFramework/Models/OpenIddictApplication.cs index 5bf26a12..5b22ba58 100644 --- a/src/OpenIddict.EntityFramework/Models/OpenIddictApplication.cs +++ b/src/OpenIddict.EntityFramework/Models/OpenIddictApplication.cs @@ -28,6 +28,18 @@ namespace OpenIddict { /// Represents an OpenIddict application. /// public class OpenIddictApplication where TKey : IEquatable { + /// + /// Gets or sets the client identifier + /// associated with the current application. + /// + public virtual string ClientId { get; set; } + + /// + /// Gets or sets the hashed client secret + /// associated with the current application. + /// + public virtual string ClientSecret { get; set; } + /// /// Gets or sets the display name /// associated with the current application. @@ -52,12 +64,6 @@ namespace OpenIddict { /// public virtual string RedirectUri { get; set; } - /// - /// Gets or sets the hashed secret - /// associated with the current application. - /// - public virtual string Secret { get; set; } - /// /// Gets the list of the tokens associated with this application. /// diff --git a/src/OpenIddict.EntityFramework/OpenIddictContext.cs b/src/OpenIddict.EntityFramework/OpenIddictContext.cs index 7a6ea6c4..d9e1b47e 100644 --- a/src/OpenIddict.EntityFramework/OpenIddictContext.cs +++ b/src/OpenIddict.EntityFramework/OpenIddictContext.cs @@ -158,6 +158,9 @@ namespace OpenIddict { builder.Entity(entity => { entity.HasKey(application => application.Id); + entity.HasIndex(application => application.ClientId) + .IsUnique(unique: true); + entity.HasMany(application => application.Tokens) .WithOne() .HasForeignKey("ApplicationId") diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs index b37edb96..ffefd10a 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs @@ -86,6 +86,19 @@ namespace OpenIddict { return Applications.SingleOrDefaultAsync(application => application.Id.Equals(key), cancellationToken); } + /// + /// Retrieves an application using its client identifier. + /// + /// The client identifier associated with the application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client application corresponding to the identifier. + /// + public virtual Task FindByClientIdAsync(string identifier, CancellationToken cancellationToken) { + return Applications.SingleOrDefaultAsync(application => application.ClientId.Equals(identifier), cancellationToken); + } + /// /// Retrieves an application using its post_logout_redirect_uri. /// @@ -147,7 +160,7 @@ namespace OpenIddict { throw new ArgumentNullException(nameof(application)); } - return Task.FromResult(application.Secret); + return Task.FromResult(application.ClientSecret); } /// diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictUserStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictUserStore.cs index 1d649954..6ea9ee53 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictUserStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictUserStore.cs @@ -103,13 +103,11 @@ namespace OpenIddict { // Ensure that the key type can be serialized. var converter = TypeDescriptor.GetConverter(typeof(TKey)); - if (!converter.CanConvertTo(typeof(string)) || !converter.CanConvertFrom(typeof(string))) { + if (!converter.CanConvertTo(typeof(string))) { throw new InvalidOperationException($"The '{typeof(TKey).Name}' key type is not supported."); } - var key = (TKey) converter.ConvertFromInvariantString(client); - - var application = await Applications.FirstOrDefaultAsync(entity => entity.Id.Equals(key), cancellationToken); + var application = await Applications.FirstOrDefaultAsync(entity => entity.ClientId.Equals(client), cancellationToken); if (application == null) { throw new InvalidOperationException("The application cannot be found in the database."); } diff --git a/src/OpenIddict.Mvc/OpenIddictController.cs b/src/OpenIddict.Mvc/OpenIddictController.cs index fa634a25..b9082324 100644 --- a/src/OpenIddict.Mvc/OpenIddictController.cs +++ b/src/OpenIddict.Mvc/OpenIddictController.cs @@ -57,7 +57,7 @@ namespace OpenIddict.Mvc { // Note: AspNet.Security.OpenIdConnect.Server automatically ensures an application // corresponds to the client_id specified in the authorization request using // IOpenIdConnectServerProvider.ValidateAuthorizationRequest (see OpenIddictProvider.cs). - var application = await applications.FindByIdAsync(request.ClientId); + var application = await applications.FindByClientIdAsync(request.ClientId); if (application == null) { return View("Error", new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidClient, @@ -100,7 +100,7 @@ namespace OpenIddict.Mvc { var identity = await users.CreateIdentityAsync(user, request.GetScopes()); Debug.Assert(identity != null); - var application = await applications.FindByIdAsync(request.ClientId); + var application = await applications.FindByClientIdAsync(request.ClientId); if (application == null) { return View("Error", new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidClient,