CheapASPNETHostingReview.com | Best and cheap ASP.NET Core 1.0 hosting. Good news! While the first OpenIddict alpha bits were tied to Identity, the two have been completely decoupled as part of OpenIddict beta1
and beta2
. Concretely, this means you can now use OpenIddict with your own authentication method or your own membership stack.
Get started
Register the aspnet-contrib
feed
Before coding, you’ll have to register the aspnet-contrib
MyGet feed, where the preliminary OpenIddict beta bits are currently hosted. For that, create a new NuGet.config
at the root of your solution:
1 2 3 4 5 6 7 8 9 | NuGet.config <?xml version="1.0" encoding="utf-8"?> <configuration> <packageSources> <add key="nuget.org" value="https://api.nuget.org/v3/index.json" /> <add key="aspnet-contrib" value="https://www.myget.org/F/aspnet-contrib/api/v3/index.json" /> </packageSources> </configuration> |
Update your project.json
to reference the OpenIddict packages
For this demo, you’ll need to reference 4 packages:
AspNet.Security.OAuth.Validation
, that provides the authentication middleware needed to validate the access tokens issued by OpenIddict.OpenIddict
, that references the OpenID Connect server middleware and provides the logic required to validate token requests.OpenIddict.EntityFrameworkCore
, that contains the default EntityFramework stores.OpenIddict.Mvc
, that provides an ASP.NET Core MVC binder allowing to useOpenIdConnectRequest
as an action parameter.
1 2 3 4 5 6 7 8 9 10 11 | project.json { "dependencies": { // ... "AspNet.Security.OAuth.Validation": "1.0.0-*", "OpenIddict": "1.0.0-*", "OpenIddict.EntityFrameworkCore": "1.0.0-*", "OpenIddict.Mvc": "1.0.0-*" } } |
Add OpenIddict in the ASP.NET Core application
Register the OpenIddict services in the dependency injection container
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | Startup.cs public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddDbContext<DbContext>(options => { // Configure the context to use an in-memory store. options.UseInMemoryDatabase(); // Register the entity sets needed by OpenIddict. // Note: use the generic overload if you need // to replace the default OpenIddict entities. options.UseOpenIddict(); }); services.AddOpenIddict(options => { // Register the Entity Framework stores. options.AddEntityFrameworkCoreStores<DbContext>(); // Register the ASP.NET Core MVC binder used by OpenIddict. // Note: if you don't call this method, you won't be able to // bind OpenIdConnectRequest or OpenIdConnectResponse parameters. options.AddMvcBinders(); // Enable the token endpoint. options.EnableTokenEndpoint("/connect/token"); // Enable the password flow. options.AllowPasswordFlow(); // During development, you can disable the HTTPS requirement. options.DisableHttpsRequirement(); }); } } |
Register OpenIddict and the validation middleware in the ASP.NET Core pipeline
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Startup.cs public class Startup { public void Configure(IApplicationBuilder app) { // Register the validation middleware, that is used to decrypt // the access tokens and populate the HttpContext.User property. app.UseOAuthValidation(); // Register the OpenIddict middleware. app.UseOpenIddict(); app.UseMvcWithDefaultRoute(); } } |
Make sure to always register the validation middleware very early in your pipeline: if the validation middleware is not at the right place, requests won’t be correctly authenticated when reaching the next middleware (e.g MVC).
The same remark applies to OpenIddict, that must be inserted before MVC to validate token requests before they reach your own code. If you don’t register it correctly, an exception will be thrown at runtime.
Create your own token authentication controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | public class AuthorizationController : Controller { [HttpPost("~/connect/token"), Produces("application/json")] public IActionResult Exchange(OpenIdConnectRequest request) { if (request.IsPasswordGrantType()) { // Validate the user credentials. // Note: to mitigate brute force attacks, you SHOULD strongly consider // applying a key derivation function like PBKDF2 to slow down // the password validation process. You SHOULD also consider // using a time-constant comparer to prevent timing attacks. if (request.Username != "alice@wonderland.com" || request.Password != "P@ssw0rd") { return Forbid(OpenIdConnectServerDefaults.AuthenticationScheme); } // Create a new ClaimsIdentity holding the user identity. var identity = new ClaimsIdentity( OpenIdConnectServerDefaults.AuthenticationScheme, OpenIdConnectConstants.Claims.Name, OpenIdConnectConstants.Claims.Role); // Add a "sub" claim containing the user identifier, and attach // the "access_token" destination to allow OpenIddict to store it // in the access token, so it can be retrieved from your controllers. identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "71346D62-9BA5-4B6D-9ECA-755574D628D8", OpenIdConnectConstants.Destinations.AccessToken); identity.AddClaim(OpenIdConnectConstants.Claims.Name, "Alice", OpenIdConnectConstants.Destinations.AccessToken); // ... add other claims, if necessary. var principal = new ClaimsPrincipal(identity); // Ask OpenIddict to generate a new token and return an OAuth2 token response. return SignIn(principal, OpenIdConnectServerDefaults.AuthenticationScheme); } throw new InvalidOperationException("The specified grant type is not supported."); } } |
Create an API controller
1 2 3 4 5 6 7 8 9 10 11 12 | public class ApiController : Controller { [Authorize, HttpGet("~/api/test")] public IActionResult GetMessage() { return Json(new { Subject = User.GetClaim(OpenIdConnectConstants.Claims.Subject), Name = User.Identity.Name }); } } |
Test your ASP.NET Core application
Retrieve an access token from your authentication controller
To retrieve an access token, send a POST
request to /connect/token
with the grant_type=password
parameter and the user credentials:
1 2 3 4 | POST /connect/token HTTP/1.1 Host: localhost:7096 Content-Type: application/x-www-form-urlencoded grant_type=password&username=alice%40wonderland.com&password=P%40ssw0rd |
If the credentials are valid, you’ll get a JSON response containing the access token:
1 2 3 4 5 | { "token_type": "Bearer", "access_token": "CfDJ8Ec0ZpniaHhGg0e0UUvOH9BWZSGrPoEwGd0_Lq2cse-T29YOq985IBiT5fEe5tTSgY1vxq2Z2ZJ7Ikwlpmh0Lrc4x9pqhqHBziUzsP_rkGZkn47TkNkOkzKCwZJZK5x-irH3HROwClFFTq0rgWdb8rZ2xriffNzsby4VwhxhN5soFD435KzmVYkdv-VuaLYo3QiSuexbRi2USVO9LK30vomAG6h2SAxZ7R-jYsXgf0f5gAmdYxg7w3yicv9v8DpUSBiGGRRfymTOnvGEsFJjGuuP8OlY5qzMs6wGaRWkOvCyV2CK_RZF_3TMs7LYCdMQ-dqWY5A03-03OmP8blKzlrKJMDZfrPQHuysbS931xxy8b3kjicfjNLmMHqzQzbUO4fecm4kY8PFnKozojDtqajfTp2bYhxS65bmVYROrswYeUWEKYR6LSdS1K__IDaLoMlLa-Wf6x1wjM2CchzgqbHRF0KEtdL5Ks88dAS44mp9BM6iUOEWyL7VkbazsBdlNciM5ZZB1_6qunufDW_tcaR8", "expires_in": 3600 } |
Query your API controller using a bearer token
To send an authenticated request, simply attach the bearer token to the Authorization
header using the following syntax: Authorization: Bearer [your bearer token]
(without the square brackets)
1 2 3 | GET /api/test HTTP/1.1 Host: localhost:7096 Authorization: Bearer CfDJ8Ec0ZpniaHhGg0e0UUvOH9BWZSGrPoEwGd0_Lq2cse-T29YOq985IBiT5fEe5tTSgY1vxq2Z2ZJ7Ikwlpmh0Lrc4x9pqhqHBziUzsP_rkGZkn47TkNkOkzKCwZJZK5x-irH3HROwClFFTq0rgWdb8rZ2xriffNzsby4VwhxhN5soFD435KzmVYkdv-VuaLYo3QiSuexbRi2USVO9LK30vomAG6h2SAxZ7R-jYsXgf0f5gAmdYxg7w3yicv9v8DpUSBiGGRRfymTOnvGEsFJjGuuP8OlY5qzMs6wGaRWkOvCyV2CK_RZF_3TMs7LYCdMQ-dqWY5A03-03OmP8blKzlrKJMDZfrPQHuysbS931xxy8b3kjicfjNLmMHqzQzbUO4fecm4kY8PFnKozojDtqajfTp2bYhxS65bmVYROrswYeUWEKYR6LSdS1K__IDaLoMlLa-Wf6x1wjM2CchzgqbHRF0KEtdL5Ks88dAS44mp9BM6iUOEWyL7VkbazsBdlNciM5ZZB1_6qunufDW_tcaR8 |
If the access token is valid, you’ll get a JSON payload containing the user details returned by the API:
1 2 3 4 | { "subject": "71346D62-9BA5-4B6D-9ECA-755574D628D8", "name": "Alice" } |