diff --git a/samples/Mvc.Server/Startup.cs b/samples/Mvc.Server/Startup.cs index 74de51d3..d9483681 100644 --- a/samples/Mvc.Server/Startup.cs +++ b/samples/Mvc.Server/Startup.cs @@ -81,6 +81,8 @@ namespace Mvc.Server { // Note: OpenIddict must be added after // ASP.NET Identity and the external providers. app.UseOpenIddict(builder => { + builder.Options.AllowInsecureHttp = true; + // You can customize the default Content Security Policy (CSP) by calling UseNWebsec explicitly. // This can be useful to allow your HTML views to reference remote scripts/images/styles. builder.UseNWebsec(directives => { diff --git a/src/OpenIddict.Core/OpenIddictProvider.Authentication.cs b/src/OpenIddict.Core/OpenIddictProvider.Authentication.cs index d5f224f2..acee2c1d 100644 --- a/src/OpenIddict.Core/OpenIddictProvider.Authentication.cs +++ b/src/OpenIddict.Core/OpenIddictProvider.Authentication.cs @@ -117,7 +117,7 @@ namespace OpenIddict { // Ensure the client application is listed as a valid audience in the identity token // and that the identity token corresponds to the authenticated user. - if (!principal.HasClaim(JwtRegisteredClaimNames.Aud, context.Request.ClientId) || + if (!principal.HasClaim(OpenIdConnectConstants.Claims.Audience, context.Request.ClientId) || !principal.HasClaim(ClaimTypes.NameIdentifier, context.HttpContext.User.GetClaim(ClaimTypes.NameIdentifier))) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, diff --git a/src/OpenIddict.Core/OpenIddictProvider.Session.cs b/src/OpenIddict.Core/OpenIddictProvider.Session.cs index 26f34cd5..a90f0420 100644 --- a/src/OpenIddict.Core/OpenIddictProvider.Session.cs +++ b/src/OpenIddict.Core/OpenIddictProvider.Session.cs @@ -4,6 +4,7 @@ * the license and the contributors participating to this project. */ +using System.Security.Claims; using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Extensions; using AspNet.Security.OpenIdConnect.Server; @@ -34,5 +35,32 @@ namespace OpenIddict { context.Validate(); } + + public override async Task HandleLogoutRequest([NotNull] HandleLogoutRequestContext context) { + var services = context.HttpContext.RequestServices.GetRequiredService>(); + + // When the client application sends an id_token_hint parameter, the corresponding identity can be retrieved using + // AuthenticateAsync and used as a way to determine whether the logout request has been sent by a legit caller. + // If the token cannot be extracted, don't handle the logout request at this stage and continue to the next middleware. + var principal = await context.HttpContext.Authentication.AuthenticateAsync(context.Options.AuthenticationScheme); + if (principal == null) { + return; + } + + // Ensure that the identity token corresponds to the authenticated user. If the token cannot be + // validated, don't handle the logout request at this stage and continue to the next middleware. + if (!principal.HasClaim(ClaimTypes.NameIdentifier, context.HttpContext.User.GetClaim(ClaimTypes.NameIdentifier))) { + return; + } + + // Immediately sign out the user and redirect him + // to the post_logout_redirect_uri if provided. + await services.SignIn.SignOutAsync(); + await context.HttpContext.Authentication.SignOutAsync(context.Options.AuthenticationScheme); + + // Mark the response as handled + // to skip the rest of the pipeline. + context.HandleResponse(); + } } } \ No newline at end of file diff --git a/src/OpenIddict.Core/OpenIddictServices.cs b/src/OpenIddict.Core/OpenIddictServices.cs index 929072d1..fd37c908 100644 --- a/src/OpenIddict.Core/OpenIddictServices.cs +++ b/src/OpenIddict.Core/OpenIddictServices.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace OpenIddict { /// @@ -41,6 +42,13 @@ namespace OpenIddict { get { return Services.GetRequiredService>>(); } } + /// + /// Gets the . + /// + public virtual OpenIddictOptions Options { + get { return Services.GetRequiredService>().Value; } + } + /// /// Gets the used to resolve services. /// diff --git a/src/OpenIddict.Mvc/OpenIddictController.cs b/src/OpenIddict.Mvc/OpenIddictController.cs index 1b354468..7fcb0464 100644 --- a/src/OpenIddict.Mvc/OpenIddictController.cs +++ b/src/OpenIddict.Mvc/OpenIddictController.cs @@ -8,7 +8,6 @@ using System; using System.Diagnostics; using System.Linq; using System.Security.Claims; -using System.Threading; using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Extensions; using JetBrains.Annotations; @@ -16,19 +15,14 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http.Authentication; -using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Protocols.OpenIdConnect; namespace OpenIddict.Mvc { // Note: this controller is generic and doesn't need to be marked as internal to prevent MVC from discovering it. public class OpenIddictController : Controller where TUser : class where TApplication : class { - public OpenIddictController( - [NotNull] OpenIddictServices services, - [NotNull] IOptions options) { + public OpenIddictController([NotNull] OpenIddictServices services) { Services = services; - Options = options.Value; } /// @@ -36,11 +30,6 @@ namespace OpenIddict.Mvc { /// protected virtual OpenIddictServices Services { get; } - /// - /// Gets the OpenIddict options used by the server. - /// - protected virtual OpenIddictOptions Options { get; } - [HttpGet, HttpPost] public virtual async Task Authorize() { // Note: when a fatal error occurs during the request processing, an OpenID Connect response @@ -127,7 +116,7 @@ namespace OpenIddict.Mvc { var ticket = new AuthenticationTicket( new ClaimsPrincipal(identity), new AuthenticationProperties(), - Options.AuthenticationScheme); + Services.Options.AuthenticationScheme); ticket.SetResources(request.GetResources()); ticket.SetScopes(request.GetScopes()); @@ -139,59 +128,54 @@ namespace OpenIddict.Mvc { } [Authorize, HttpPost, ValidateAntiForgeryToken] - public virtual IActionResult Deny() { + public virtual Task Deny() { var response = HttpContext.GetOpenIdConnectResponse(); if (response != null) { - return View("Error", response); + return Task.FromResult(View("Error", response)); } var request = HttpContext.GetOpenIdConnectRequest(); if (request == null) { - return View("Error", new OpenIdConnectMessage { + return Task.FromResult(View("Error", new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.ServerError, ErrorDescription = "An internal error has occurred" - }); + })); } // Notify ASOS that the authorization grant has been denied by the resource owner. // Note: OpenIdConnectServerHandler will automatically take care of redirecting // the user agent to the client application using the appropriate response_mode. - return Forbid(Options.AuthenticationScheme); + return Task.FromResult(Forbid(Services.Options.AuthenticationScheme)); } [HttpGet] - public virtual async Task Logout() { + public virtual Task Logout() { var response = HttpContext.GetOpenIdConnectResponse(); if (response != null) { - return View("Error", response); + return Task.FromResult(View("Error", response)); } - // When invoked, the logout endpoint might receive an unauthenticated request if the server cookie has expired. - // When the client application sends an id_token_hint parameter, the corresponding identity can be retrieved - // using AuthenticateAsync or using User when the authorization server is declared as AuthenticationMode.Active. - var identity = await HttpContext.Authentication.AuthenticateAsync(Options.AuthenticationScheme); - var request = HttpContext.GetOpenIdConnectRequest(); if (request == null) { - return View("Error", new OpenIdConnectMessage { + return Task.FromResult(View("Error", new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.ServerError, ErrorDescription = "An internal error has occurred" - }); + })); } - return View("Logout", Tuple.Create(request, identity)); + return Task.FromResult(View("Logout", request)); } [HttpPost, ValidateAntiForgeryToken] - public virtual async Task Logout([FromServices] SignInManager manager) { + public virtual async Task Signout() { // Instruct the cookies middleware to delete the local cookie created // when the user agent is redirected from the external identity provider // after a successful authentication flow (e.g Google or Facebook). - await manager.SignOutAsync(); + await Services.SignIn.SignOutAsync(); // Returning a SignOutResult will ask ASOS to redirect the user agent // to the post_logout_redirect_uri specified by the client application. - return SignOut(Options.AuthenticationScheme); + return SignOut(Services.Options.AuthenticationScheme); } } } \ No newline at end of file diff --git a/src/OpenIddict.Mvc/Views/Shared/Logout.cshtml b/src/OpenIddict.Mvc/Views/Shared/Logout.cshtml index 78a9a3a5..e8a727c1 100644 --- a/src/OpenIddict.Mvc/Views/Shared/Logout.cshtml +++ b/src/OpenIddict.Mvc/Views/Shared/Logout.cshtml @@ -1,16 +1,15 @@ -
+@using Microsoft.IdentityModel.Protocols.OpenIdConnect +@model OpenIdConnectMessage + +

Log out

Are you sure you want to sign out?

- @if (Model.Item2 != null && Model.Item2.Identity != null) { -

Connected user: @Model.Item2.Identity.Name

- } - -
+ @Html.AntiForgeryToken() @* Flow the logout request *@ - @foreach (var parameter in Model.Item1.Parameters) { + @foreach (var parameter in Model.Parameters) { }