Add RemoteAuthenticationHandler base/error handling logic
This commit is contained in:
parent
bfc1fcf421
commit
409b50269a
|
|
@ -21,7 +21,7 @@ namespace CookieSample
|
|||
|
||||
app.UseCookieAuthentication(options =>
|
||||
{
|
||||
options.AutomaticAuthentication = true;
|
||||
options.AutomaticAuthenticate = true;
|
||||
});
|
||||
|
||||
app.Run(async context =>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ namespace CookieSessionSample
|
|||
|
||||
app.UseCookieAuthentication(options =>
|
||||
{
|
||||
options.AutomaticAuthentication = true;
|
||||
options.AutomaticAuthenticate = true;
|
||||
options.SessionStore = new MemoryCacheTicketStore();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ namespace OpenIdConnectSample
|
|||
|
||||
app.UseCookieAuthentication(options =>
|
||||
{
|
||||
options.AutomaticAuthentication = true;
|
||||
options.AutomaticAuthenticate = true;
|
||||
});
|
||||
|
||||
app.UseOpenIdConnectAuthentication(options =>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ namespace CookieSample
|
|||
|
||||
app.UseCookieAuthentication(options =>
|
||||
{
|
||||
options.AutomaticAuthentication = true;
|
||||
options.AutomaticAuthenticate = true;
|
||||
options.AutomaticChallenge = true;
|
||||
options.LoginPath = new PathString("/login");
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -101,43 +101,28 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
return ticket;
|
||||
}
|
||||
|
||||
protected override async Task<AuthenticationTicket> HandleAuthenticateAsync()
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
AuthenticationTicket ticket = null;
|
||||
try
|
||||
var ticket = await EnsureCookieTicket();
|
||||
if (ticket == null)
|
||||
{
|
||||
ticket = await EnsureCookieTicket();
|
||||
if (ticket == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var context = new CookieValidatePrincipalContext(Context, ticket, Options);
|
||||
await Options.Events.ValidatePrincipal(context);
|
||||
|
||||
if (context.Principal == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (context.ShouldRenew)
|
||||
{
|
||||
_shouldRenew = true;
|
||||
}
|
||||
|
||||
return new AuthenticationTicket(context.Principal, context.Properties, Options.AuthenticationScheme);
|
||||
return AuthenticateResult.Failed("No ticket.");
|
||||
}
|
||||
catch (Exception exception)
|
||||
|
||||
var context = new CookieValidatePrincipalContext(Context, ticket, Options);
|
||||
await Options.Events.ValidatePrincipal(context);
|
||||
|
||||
if (context.Principal == null)
|
||||
{
|
||||
var exceptionContext = new CookieExceptionContext(Context, Options,
|
||||
CookieExceptionContext.ExceptionLocation.Authenticate, exception, ticket);
|
||||
await Options.Events.Exception(exceptionContext);
|
||||
if (exceptionContext.Rethrow)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
return exceptionContext.Ticket;
|
||||
return AuthenticateResult.Failed("No principal.");
|
||||
}
|
||||
|
||||
if (context.ShouldRenew)
|
||||
{
|
||||
_shouldRenew = true;
|
||||
}
|
||||
|
||||
return AuthenticateResult.Success(new AuthenticationTicket(context.Principal, context.Properties, Options.AuthenticationScheme));
|
||||
}
|
||||
|
||||
private CookieOptions BuildCookieOptions()
|
||||
|
|
@ -167,8 +152,9 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
return;
|
||||
}
|
||||
|
||||
var ticket = await HandleAuthenticateOnceAsync();
|
||||
try
|
||||
// REVIEW: Should this check if there was an error, and then if that error was already handled??
|
||||
var ticket = (await HandleAuthenticateOnceAsync())?.Ticket;
|
||||
if (ticket != null)
|
||||
{
|
||||
if (_renewIssuedUtc.HasValue)
|
||||
{
|
||||
|
|
@ -205,141 +191,105 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
|
||||
await ApplyHeaders(shouldRedirectToReturnUrl: false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var exceptionContext = new CookieExceptionContext(Context, Options,
|
||||
CookieExceptionContext.ExceptionLocation.FinishResponse, exception, ticket);
|
||||
await Options.Events.Exception(exceptionContext);
|
||||
if (exceptionContext.Rethrow)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task HandleSignInAsync(SignInContext signin)
|
||||
{
|
||||
var ticket = await EnsureCookieTicket();
|
||||
try
|
||||
var cookieOptions = BuildCookieOptions();
|
||||
|
||||
var signInContext = new CookieSigningInContext(
|
||||
Context,
|
||||
Options,
|
||||
Options.AuthenticationScheme,
|
||||
signin.Principal,
|
||||
new AuthenticationProperties(signin.Properties),
|
||||
cookieOptions);
|
||||
|
||||
DateTimeOffset issuedUtc;
|
||||
if (signInContext.Properties.IssuedUtc.HasValue)
|
||||
{
|
||||
var cookieOptions = BuildCookieOptions();
|
||||
|
||||
var signInContext = new CookieSigningInContext(
|
||||
Context,
|
||||
Options,
|
||||
Options.AuthenticationScheme,
|
||||
signin.Principal,
|
||||
new AuthenticationProperties(signin.Properties),
|
||||
cookieOptions);
|
||||
|
||||
DateTimeOffset issuedUtc;
|
||||
if (signInContext.Properties.IssuedUtc.HasValue)
|
||||
{
|
||||
issuedUtc = signInContext.Properties.IssuedUtc.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
issuedUtc = Options.SystemClock.UtcNow;
|
||||
signInContext.Properties.IssuedUtc = issuedUtc;
|
||||
}
|
||||
|
||||
if (!signInContext.Properties.ExpiresUtc.HasValue)
|
||||
{
|
||||
signInContext.Properties.ExpiresUtc = issuedUtc.Add(Options.ExpireTimeSpan);
|
||||
}
|
||||
|
||||
await Options.Events.SigningIn(signInContext);
|
||||
|
||||
if (signInContext.Properties.IsPersistent)
|
||||
{
|
||||
var expiresUtc = signInContext.Properties.ExpiresUtc ?? issuedUtc.Add(Options.ExpireTimeSpan);
|
||||
signInContext.CookieOptions.Expires = expiresUtc.ToUniversalTime().DateTime;
|
||||
}
|
||||
|
||||
ticket = new AuthenticationTicket(signInContext.Principal, signInContext.Properties, signInContext.AuthenticationScheme);
|
||||
if (Options.SessionStore != null)
|
||||
{
|
||||
if (_sessionKey != null)
|
||||
{
|
||||
await Options.SessionStore.RemoveAsync(_sessionKey);
|
||||
}
|
||||
_sessionKey = await Options.SessionStore.StoreAsync(ticket);
|
||||
var principal = new ClaimsPrincipal(
|
||||
new ClaimsIdentity(
|
||||
new[] { new Claim(SessionIdClaim, _sessionKey, ClaimValueTypes.String, Options.ClaimsIssuer) },
|
||||
Options.ClaimsIssuer));
|
||||
ticket = new AuthenticationTicket(principal, null, Options.AuthenticationScheme);
|
||||
}
|
||||
var cookieValue = Options.TicketDataFormat.Protect(ticket);
|
||||
|
||||
Options.CookieManager.AppendResponseCookie(
|
||||
Context,
|
||||
Options.CookieName,
|
||||
cookieValue,
|
||||
signInContext.CookieOptions);
|
||||
|
||||
var signedInContext = new CookieSignedInContext(
|
||||
Context,
|
||||
Options,
|
||||
Options.AuthenticationScheme,
|
||||
signInContext.Principal,
|
||||
signInContext.Properties);
|
||||
|
||||
await Options.Events.SignedIn(signedInContext);
|
||||
|
||||
// Only redirect on the login path
|
||||
var shouldRedirect = Options.LoginPath.HasValue && OriginalPath == Options.LoginPath;
|
||||
await ApplyHeaders(shouldRedirect);
|
||||
issuedUtc = signInContext.Properties.IssuedUtc.Value;
|
||||
}
|
||||
catch (Exception exception)
|
||||
else
|
||||
{
|
||||
var exceptionContext = new CookieExceptionContext(Context, Options,
|
||||
CookieExceptionContext.ExceptionLocation.SignIn, exception, ticket);
|
||||
await Options.Events.Exception(exceptionContext);
|
||||
if (exceptionContext.Rethrow)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
issuedUtc = Options.SystemClock.UtcNow;
|
||||
signInContext.Properties.IssuedUtc = issuedUtc;
|
||||
}
|
||||
|
||||
if (!signInContext.Properties.ExpiresUtc.HasValue)
|
||||
{
|
||||
signInContext.Properties.ExpiresUtc = issuedUtc.Add(Options.ExpireTimeSpan);
|
||||
}
|
||||
|
||||
await Options.Events.SigningIn(signInContext);
|
||||
|
||||
if (signInContext.Properties.IsPersistent)
|
||||
{
|
||||
var expiresUtc = signInContext.Properties.ExpiresUtc ?? issuedUtc.Add(Options.ExpireTimeSpan);
|
||||
signInContext.CookieOptions.Expires = expiresUtc.ToUniversalTime().DateTime;
|
||||
}
|
||||
|
||||
ticket = new AuthenticationTicket(signInContext.Principal, signInContext.Properties, signInContext.AuthenticationScheme);
|
||||
if (Options.SessionStore != null)
|
||||
{
|
||||
if (_sessionKey != null)
|
||||
{
|
||||
await Options.SessionStore.RemoveAsync(_sessionKey);
|
||||
}
|
||||
_sessionKey = await Options.SessionStore.StoreAsync(ticket);
|
||||
var principal = new ClaimsPrincipal(
|
||||
new ClaimsIdentity(
|
||||
new[] { new Claim(SessionIdClaim, _sessionKey, ClaimValueTypes.String, Options.ClaimsIssuer) },
|
||||
Options.ClaimsIssuer));
|
||||
ticket = new AuthenticationTicket(principal, null, Options.AuthenticationScheme);
|
||||
}
|
||||
var cookieValue = Options.TicketDataFormat.Protect(ticket);
|
||||
|
||||
Options.CookieManager.AppendResponseCookie(
|
||||
Context,
|
||||
Options.CookieName,
|
||||
cookieValue,
|
||||
signInContext.CookieOptions);
|
||||
|
||||
var signedInContext = new CookieSignedInContext(
|
||||
Context,
|
||||
Options,
|
||||
Options.AuthenticationScheme,
|
||||
signInContext.Principal,
|
||||
signInContext.Properties);
|
||||
|
||||
await Options.Events.SignedIn(signedInContext);
|
||||
|
||||
// Only redirect on the login path
|
||||
var shouldRedirect = Options.LoginPath.HasValue && OriginalPath == Options.LoginPath;
|
||||
await ApplyHeaders(shouldRedirect);
|
||||
}
|
||||
|
||||
protected override async Task HandleSignOutAsync(SignOutContext signOutContext)
|
||||
{
|
||||
var ticket = await EnsureCookieTicket();
|
||||
try
|
||||
var cookieOptions = BuildCookieOptions();
|
||||
if (Options.SessionStore != null && _sessionKey != null)
|
||||
{
|
||||
var cookieOptions = BuildCookieOptions();
|
||||
if (Options.SessionStore != null && _sessionKey != null)
|
||||
{
|
||||
await Options.SessionStore.RemoveAsync(_sessionKey);
|
||||
}
|
||||
|
||||
var context = new CookieSigningOutContext(
|
||||
Context,
|
||||
Options,
|
||||
cookieOptions);
|
||||
|
||||
await Options.Events.SigningOut(context);
|
||||
|
||||
Options.CookieManager.DeleteCookie(
|
||||
Context,
|
||||
Options.CookieName,
|
||||
context.CookieOptions);
|
||||
|
||||
// Only redirect on the logout path
|
||||
var shouldRedirect = Options.LogoutPath.HasValue && OriginalPath == Options.LogoutPath;
|
||||
await ApplyHeaders(shouldRedirect);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var exceptionContext = new CookieExceptionContext(Context, Options,
|
||||
CookieExceptionContext.ExceptionLocation.SignOut, exception, ticket);
|
||||
await Options.Events.Exception(exceptionContext);
|
||||
if (exceptionContext.Rethrow)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
await Options.SessionStore.RemoveAsync(_sessionKey);
|
||||
}
|
||||
|
||||
var context = new CookieSigningOutContext(
|
||||
Context,
|
||||
Options,
|
||||
cookieOptions);
|
||||
|
||||
await Options.Events.SigningOut(context);
|
||||
|
||||
Options.CookieManager.DeleteCookie(
|
||||
Context,
|
||||
Options.CookieName,
|
||||
context.CookieOptions);
|
||||
|
||||
// Only redirect on the logout path
|
||||
var shouldRedirect = Options.LogoutPath.HasValue && OriginalPath == Options.LogoutPath;
|
||||
await ApplyHeaders(shouldRedirect);
|
||||
}
|
||||
|
||||
private async Task ApplyHeaders(bool shouldRedirectToReturnUrl)
|
||||
|
|
@ -374,30 +324,17 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
return path[0] == '/' && path[1] != '/' && path[1] != '\\';
|
||||
}
|
||||
|
||||
protected async override Task<bool> HandleForbiddenAsync(ChallengeContext context)
|
||||
protected override async Task<bool> HandleForbiddenAsync(ChallengeContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var accessDeniedUri =
|
||||
Request.Scheme +
|
||||
"://" +
|
||||
Request.Host +
|
||||
OriginalPathBase +
|
||||
Options.AccessDeniedPath;
|
||||
var accessDeniedUri =
|
||||
Request.Scheme +
|
||||
"://" +
|
||||
Request.Host +
|
||||
OriginalPathBase +
|
||||
Options.AccessDeniedPath;
|
||||
|
||||
var redirectContext = new CookieRedirectContext(Context, Options, accessDeniedUri);
|
||||
await Options.Events.RedirectToAccessDenied(redirectContext);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var exceptionContext = new CookieExceptionContext(Context, Options,
|
||||
CookieExceptionContext.ExceptionLocation.Forbidden, exception, ticket: null);
|
||||
await Options.Events.Exception(exceptionContext);
|
||||
if (exceptionContext.Rethrow)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
var redirectContext = new CookieRedirectContext(Context, Options, accessDeniedUri);
|
||||
await Options.Events.RedirectToAccessDenied(redirectContext);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -409,28 +346,16 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
}
|
||||
|
||||
var redirectUri = new AuthenticationProperties(context.Properties).RedirectUri;
|
||||
try
|
||||
if (string.IsNullOrEmpty(redirectUri))
|
||||
{
|
||||
if (string.IsNullOrEmpty(redirectUri))
|
||||
{
|
||||
redirectUri = OriginalPathBase + Request.Path + Request.QueryString;
|
||||
}
|
||||
redirectUri = OriginalPathBase + Request.Path + Request.QueryString;
|
||||
}
|
||||
|
||||
var loginUri = Options.LoginPath + QueryString.Create(Options.ReturnUrlParameter, redirectUri);
|
||||
var redirectContext = new CookieRedirectContext(Context, Options, BuildRedirectUri(loginUri));
|
||||
await Options.Events.RedirectToLogin(redirectContext);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var exceptionContext = new CookieExceptionContext(Context, Options,
|
||||
CookieExceptionContext.ExceptionLocation.Unauthorized, exception, ticket: null);
|
||||
await Options.Events.Exception(exceptionContext);
|
||||
if (exceptionContext.Rethrow)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
var loginUri = Options.LoginPath + QueryString.Create(Options.ReturnUrlParameter, redirectUri);
|
||||
var redirectContext = new CookieRedirectContext(Context, Options, BuildRedirectUri(loginUri));
|
||||
await Options.Events.RedirectToLogin(redirectContext);
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Authentication;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication.Cookies
|
||||
{
|
||||
public class BaseCookieContext : BaseContext
|
||||
{
|
||||
public BaseCookieContext(
|
||||
HttpContext context,
|
||||
CookieAuthenticationOptions options)
|
||||
: base(context)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
Options = options;
|
||||
}
|
||||
|
||||
public CookieAuthenticationOptions Options { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -42,11 +42,6 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A delegate assigned to this property will be invoked when the related method is called.
|
||||
/// </summary>
|
||||
public Func<CookieExceptionContext, Task> OnException { get; set; } = context => Task.FromResult(0);
|
||||
|
||||
/// <summary>
|
||||
/// Implements the interface method by invoking the related delegate method.
|
||||
/// </summary>
|
||||
|
|
@ -95,11 +90,5 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
/// </summary>
|
||||
/// <param name="context">Contains information about the event</param>
|
||||
public virtual Task RedirectToAccessDenied(CookieRedirectContext context) => OnRedirect(context);
|
||||
|
||||
/// <summary>
|
||||
/// Implements the interface method by invoking the related delegate method.
|
||||
/// </summary>
|
||||
/// <param name="context">Contains information about the event</param>
|
||||
public virtual Task Exception(CookieExceptionContext context) => OnException(context);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication.Cookies
|
||||
{
|
||||
/// <summary>
|
||||
/// Context object passed to the ICookieAuthenticationProvider method Exception.
|
||||
/// </summary>
|
||||
public class CookieExceptionContext : BaseContext<CookieAuthenticationOptions>
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the context object.
|
||||
/// </summary>
|
||||
/// <param name="context">The HTTP request context</param>
|
||||
/// <param name="options">The middleware options</param>
|
||||
/// <param name="location">The location of the exception</param>
|
||||
/// <param name="exception">The exception thrown.</param>
|
||||
/// <param name="ticket">The current ticket, if any.</param>
|
||||
public CookieExceptionContext(
|
||||
HttpContext context,
|
||||
CookieAuthenticationOptions options,
|
||||
ExceptionLocation location,
|
||||
Exception exception,
|
||||
AuthenticationTicket ticket)
|
||||
: base(context, options)
|
||||
{
|
||||
Location = location;
|
||||
Exception = exception;
|
||||
Rethrow = true;
|
||||
Ticket = ticket;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The code paths where exceptions may be reported.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Scope = "type",
|
||||
Target = "Microsoft.Owin.Security.Cookies.CookieExceptionContext+ExceptionLocation", Justification = "It is a directly related option.")]
|
||||
public enum ExceptionLocation
|
||||
{
|
||||
/// <summary>
|
||||
/// The exception was reported in the Authenticate code path.
|
||||
/// </summary>
|
||||
Authenticate,
|
||||
|
||||
/// <summary>
|
||||
/// The exception was reported in the FinishResponse code path, during sign-in, sign-out, or refresh.
|
||||
/// </summary>
|
||||
FinishResponse,
|
||||
|
||||
/// <summary>
|
||||
/// The exception was reported in the Unauthorized code path, during redirect generation.
|
||||
/// </summary>
|
||||
Unauthorized,
|
||||
|
||||
/// <summary>
|
||||
/// The exception was reported in the Forbidden code path, during redirect generation.
|
||||
/// </summary>
|
||||
Forbidden,
|
||||
|
||||
/// <summary>
|
||||
/// The exception was reported in the SignIn code path
|
||||
/// </summary>
|
||||
SignIn,
|
||||
|
||||
/// <summary>
|
||||
/// The exception was reported in the SignOut code path
|
||||
/// </summary>
|
||||
SignOut,
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The code path the exception occurred in.
|
||||
/// </summary>
|
||||
public ExceptionLocation Location { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The exception thrown.
|
||||
/// </summary>
|
||||
public Exception Exception { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the exception should be re-thrown (default), false if it should be suppressed.
|
||||
/// </summary>
|
||||
public bool Rethrow { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current authentication ticket, if any.
|
||||
/// In the AuthenticateAsync code path, if the given exception is not re-thrown then this ticket
|
||||
/// will be returned to the application. The ticket may be replaced if needed.
|
||||
/// </summary>
|
||||
public AuthenticationTicket Ticket { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
/// <summary>
|
||||
/// Context passed when a Challenge, SignIn, or SignOut causes a redirect in the cookie middleware
|
||||
/// </summary>
|
||||
public class CookieRedirectContext : BaseContext<CookieAuthenticationOptions>
|
||||
public class CookieRedirectContext : BaseCookieContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new context object.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
/// <summary>
|
||||
/// Context object passed to the ICookieAuthenticationEvents method SignedIn.
|
||||
/// </summary>
|
||||
public class CookieSignedInContext : BaseContext<CookieAuthenticationOptions>
|
||||
public class CookieSignedInContext : BaseCookieContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the context object.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
/// <summary>
|
||||
/// Context object passed to the ICookieAuthenticationEvents method SigningIn.
|
||||
/// </summary>
|
||||
public class CookieSigningInContext : BaseContext<CookieAuthenticationOptions>
|
||||
public class CookieSigningInContext : BaseCookieContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the context object.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
/// <summary>
|
||||
/// Context object passed to the ICookieAuthenticationEvents method SigningOut
|
||||
/// </summary>
|
||||
public class CookieSigningOutContext : BaseContext<CookieAuthenticationOptions>
|
||||
public class CookieSigningOutContext : BaseCookieContext
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
/// <summary>
|
||||
/// Context object passed to the ICookieAuthenticationProvider method ValidatePrincipal.
|
||||
/// </summary>
|
||||
public class CookieValidatePrincipalContext : BaseContext<CookieAuthenticationOptions>
|
||||
public class CookieValidatePrincipalContext : BaseCookieContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the context object.
|
||||
|
|
|
|||
|
|
@ -60,11 +60,5 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
/// </summary>
|
||||
/// <param name="context">Contains information about the login session as well as information about the authentication cookie.</param>
|
||||
Task SigningOut(CookieSigningOutContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Called when an exception occurs during request or response processing.
|
||||
/// </summary>
|
||||
/// <param name="context">Contains information about the exception that occurred</param>
|
||||
Task Exception(CookieExceptionContext context);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using Microsoft.AspNet.Http;
|
|||
|
||||
namespace Microsoft.AspNet.Authentication.JwtBearer
|
||||
{
|
||||
public class AuthenticationFailedContext : BaseControlContext<JwtBearerOptions>
|
||||
public class AuthenticationFailedContext : BaseJwtBearerContext
|
||||
{
|
||||
public AuthenticationFailedContext(HttpContext context, JwtBearerOptions options)
|
||||
: base(context, options)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication.JwtBearer
|
||||
{
|
||||
public class BaseJwtBearerContext : BaseControlContext
|
||||
{
|
||||
public BaseJwtBearerContext(HttpContext context, JwtBearerOptions options)
|
||||
: base(context)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
Options = options;
|
||||
}
|
||||
|
||||
public JwtBearerOptions Options { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ using Microsoft.AspNet.Http;
|
|||
|
||||
namespace Microsoft.AspNet.Authentication.JwtBearer
|
||||
{
|
||||
public class JwtBearerChallengeContext : BaseControlContext<JwtBearerOptions>
|
||||
public class JwtBearerChallengeContext : BaseJwtBearerContext
|
||||
{
|
||||
public JwtBearerChallengeContext(HttpContext context, JwtBearerOptions options)
|
||||
: base(context, options)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using Microsoft.AspNet.Http;
|
|||
|
||||
namespace Microsoft.AspNet.Authentication.JwtBearer
|
||||
{
|
||||
public class ReceivedTokenContext : BaseControlContext<JwtBearerOptions>
|
||||
public class ReceivedTokenContext : BaseJwtBearerContext
|
||||
{
|
||||
public ReceivedTokenContext(HttpContext context, JwtBearerOptions options)
|
||||
: base(context, options)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using Microsoft.AspNet.Http;
|
|||
|
||||
namespace Microsoft.AspNet.Authentication.JwtBearer
|
||||
{
|
||||
public class ReceivingTokenContext : BaseControlContext<JwtBearerOptions>
|
||||
public class ReceivingTokenContext : BaseJwtBearerContext
|
||||
{
|
||||
public ReceivingTokenContext(HttpContext context, JwtBearerOptions options)
|
||||
: base(context, options)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using Microsoft.AspNet.Http;
|
|||
|
||||
namespace Microsoft.AspNet.Authentication.JwtBearer
|
||||
{
|
||||
public class ValidatedTokenContext : BaseControlContext<JwtBearerOptions>
|
||||
public class ValidatedTokenContext : BaseJwtBearerContext
|
||||
{
|
||||
public ValidatedTokenContext(HttpContext context, JwtBearerOptions options)
|
||||
: base(context, options)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
|
|||
/// Searches the 'Authorization' header for a 'Bearer' token. If the 'Bearer' token is found, it is validated using <see cref="TokenValidationParameters"/> set in the options.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override async Task<AuthenticationTicket> HandleAuthenticateAsync()
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
string token = null;
|
||||
try
|
||||
|
|
@ -32,12 +32,12 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
|
|||
await Options.Events.ReceivingToken(receivingTokenContext);
|
||||
if (receivingTokenContext.HandledResponse)
|
||||
{
|
||||
return receivingTokenContext.AuthenticationTicket;
|
||||
return AuthenticateResult.Success(receivingTokenContext.AuthenticationTicket);
|
||||
}
|
||||
|
||||
if (receivingTokenContext.Skipped)
|
||||
{
|
||||
return null;
|
||||
return AuthenticateResult.Success(ticket: null);
|
||||
}
|
||||
|
||||
// If application retrieved token from somewhere else, use that.
|
||||
|
|
@ -50,7 +50,7 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
|
|||
// If no authorization header found, nothing to process further
|
||||
if (string.IsNullOrEmpty(authorization))
|
||||
{
|
||||
return null;
|
||||
return AuthenticateResult.Failed("No authorization header.");
|
||||
}
|
||||
|
||||
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
|
||||
|
|
@ -61,7 +61,7 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
|
|||
// If no token found, no further work possible
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
return null;
|
||||
return AuthenticateResult.Failed("No bearer token.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -74,12 +74,12 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
|
|||
await Options.Events.ReceivedToken(receivedTokenContext);
|
||||
if (receivedTokenContext.HandledResponse)
|
||||
{
|
||||
return receivedTokenContext.AuthenticationTicket;
|
||||
return AuthenticateResult.Success(receivedTokenContext.AuthenticationTicket);
|
||||
}
|
||||
|
||||
if (receivedTokenContext.Skipped)
|
||||
{
|
||||
return null;
|
||||
return AuthenticateResult.Success(ticket: null);
|
||||
}
|
||||
|
||||
if (_configuration == null && Options.ConfigurationManager != null)
|
||||
|
|
@ -118,18 +118,19 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
|
|||
await Options.Events.ValidatedToken(validatedTokenContext);
|
||||
if (validatedTokenContext.HandledResponse)
|
||||
{
|
||||
return validatedTokenContext.AuthenticationTicket;
|
||||
return AuthenticateResult.Success(validatedTokenContext.AuthenticationTicket);
|
||||
}
|
||||
|
||||
if (validatedTokenContext.Skipped)
|
||||
{
|
||||
return null;
|
||||
return AuthenticateResult.Success(ticket: null);
|
||||
}
|
||||
|
||||
return ticket;
|
||||
return AuthenticateResult.Success(ticket);
|
||||
}
|
||||
}
|
||||
|
||||
// REVIEW: this maybe return an error instead?
|
||||
throw new InvalidOperationException("No SecurityTokenValidator available for token: " + token ?? "null");
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -150,12 +151,11 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
|
|||
await Options.Events.AuthenticationFailed(authenticationFailedContext);
|
||||
if (authenticationFailedContext.HandledResponse)
|
||||
{
|
||||
return authenticationFailedContext.AuthenticationTicket;
|
||||
return AuthenticateResult.Success(authenticationFailedContext.AuthenticationTicket);
|
||||
}
|
||||
|
||||
if (authenticationFailedContext.Skipped)
|
||||
{
|
||||
return null;
|
||||
return AuthenticateResult.Success(ticket: null);
|
||||
}
|
||||
|
||||
throw;
|
||||
|
|
|
|||
|
|
@ -1,113 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication.OAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class used for certain event contexts
|
||||
/// </summary>
|
||||
public abstract class BaseValidatingContext<TOptions> : BaseContext<TOptions>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes base class used for certain event contexts
|
||||
/// </summary>
|
||||
protected BaseValidatingContext(
|
||||
HttpContext context,
|
||||
TOptions options)
|
||||
: base(context, options)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if application code has called any of the Validate methods on this context.
|
||||
/// </summary>
|
||||
public bool IsValidated { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if application code has called any of the SetError methods on this context.
|
||||
/// </summary>
|
||||
public bool HasError { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The error argument provided when SetError was called on this context. This is eventually
|
||||
/// returned to the client app as the OAuth "error" parameter.
|
||||
/// </summary>
|
||||
public string Error { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The optional errorDescription argument provided when SetError was called on this context. This is eventually
|
||||
/// returned to the client app as the OAuth "error_description" parameter.
|
||||
/// </summary>
|
||||
public string ErrorDescription { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The optional errorUri argument provided when SetError was called on this context. This is eventually
|
||||
/// returned to the client app as the OAuth "error_uri" parameter.
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "error_uri is a string value in the protocol")]
|
||||
public string ErrorUri { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Marks this context as validated by the application. IsValidated becomes true and HasError becomes false as a result of calling.
|
||||
/// </summary>
|
||||
/// <returns>True if the validation has taken effect.</returns>
|
||||
public virtual bool Validated()
|
||||
{
|
||||
IsValidated = true;
|
||||
HasError = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks this context as not validated by the application. IsValidated and HasError become false as a result of calling.
|
||||
/// </summary>
|
||||
public virtual void Rejected()
|
||||
{
|
||||
IsValidated = false;
|
||||
HasError = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks this context as not validated by the application and assigns various error information properties.
|
||||
/// HasError becomes true and IsValidated becomes false as a result of calling.
|
||||
/// </summary>
|
||||
/// <param name="error">Assigned to the Error property</param>
|
||||
public void SetError(string error)
|
||||
{
|
||||
SetError(error, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks this context as not validated by the application and assigns various error information properties.
|
||||
/// HasError becomes true and IsValidated becomes false as a result of calling.
|
||||
/// </summary>
|
||||
/// <param name="error">Assigned to the Error property</param>
|
||||
/// <param name="errorDescription">Assigned to the ErrorDescription property</param>
|
||||
public void SetError(string error,
|
||||
string errorDescription)
|
||||
{
|
||||
SetError(error, errorDescription, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks this context as not validated by the application and assigns various error information properties.
|
||||
/// HasError becomes true and IsValidated becomes false as a result of calling.
|
||||
/// </summary>
|
||||
/// <param name="error">Assigned to the Error property</param>
|
||||
/// <param name="errorDescription">Assigned to the ErrorDescription property</param>
|
||||
/// <param name="errorUri">Assigned to the ErrorUri property</param>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "2#", Justification = "error_uri is a string value in the protocol")]
|
||||
public void SetError(string error,
|
||||
string errorDescription,
|
||||
string errorUri)
|
||||
{
|
||||
Error = error;
|
||||
ErrorDescription = errorDescription;
|
||||
ErrorUri = errorUri;
|
||||
Rejected();
|
||||
HasError = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Authentication;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication.OAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class used for certain event contexts
|
||||
/// </summary>
|
||||
public abstract class BaseValidatingTicketContext<TOptions> : BaseValidatingContext<TOptions>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes base class used for certain event contexts
|
||||
/// </summary>
|
||||
protected BaseValidatingTicketContext(
|
||||
HttpContext context,
|
||||
TOptions options,
|
||||
AuthenticationTicket ticket)
|
||||
: base(context, options)
|
||||
{
|
||||
Ticket = ticket;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains the identity and properties for the application to authenticate. If the Validated method
|
||||
/// is invoked with an AuthenticationTicket or ClaimsIdentity argument, that new value is assigned to
|
||||
/// this property in addition to changing IsValidated to true.
|
||||
/// </summary>
|
||||
public AuthenticationTicket Ticket { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the ticket information on this context and marks it as as validated by the application.
|
||||
/// IsValidated becomes true and HasError becomes false as a result of calling.
|
||||
/// </summary>
|
||||
/// <param name="ticket">Assigned to the Ticket property</param>
|
||||
/// <returns>True if the validation has taken effect.</returns>
|
||||
public bool Validated(AuthenticationTicket ticket)
|
||||
{
|
||||
Ticket = ticket;
|
||||
return Validated();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Alters the ticket information on this context and marks it as as validated by the application.
|
||||
/// IsValidated becomes true and HasError becomes false as a result of calling.
|
||||
/// </summary>
|
||||
/// <param name="identity">Assigned to the Ticket.Identity property</param>
|
||||
/// <returns>True if the validation has taken effect.</returns>
|
||||
public bool Validated(ClaimsPrincipal principal)
|
||||
{
|
||||
AuthenticationProperties properties = Ticket != null ? Ticket.Properties : new AuthenticationProperties();
|
||||
// TODO: Ticket can be null, need to revisit
|
||||
return Validated(new AuthenticationTicket(principal, properties, Ticket.AuthenticationScheme));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
|||
/// <summary>
|
||||
/// Specifies callback methods which the <see cref="OAuthMiddleware"/> invokes to enable developer control over the authentication process.
|
||||
/// </summary>
|
||||
public interface IOAuthEvents
|
||||
public interface IOAuthEvents : IRemoteAuthenticationEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoked after the provider successfully authenticates a user. This can be used to retrieve user information.
|
||||
|
|
@ -18,13 +18,6 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
|||
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
|
||||
Task CreatingTicket(OAuthCreatingTicketContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Invoked prior to the <see cref="ClaimsIdentity"/> being saved in a local cookie and the browser being redirected to the originally requested URL.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
|
||||
Task SigningIn(SigningInContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Called when a Challenge causes a redirect to the authorize endpoint.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication.OAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the HTTP response header for the bearer authentication scheme.
|
||||
/// </summary>
|
||||
public class OAuthChallengeContext : BaseContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="OAuthRequestTokenContext"/>
|
||||
/// </summary>
|
||||
/// <param name="context">HTTP environment</param>
|
||||
/// <param name="challenge">The www-authenticate header value.</param>
|
||||
public OAuthChallengeContext(
|
||||
HttpContext context,
|
||||
string challenge)
|
||||
: base(context)
|
||||
{
|
||||
Challenge = challenge;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The www-authenticate header value.
|
||||
/// </summary>
|
||||
public string Challenge { get; protected set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
|||
/// <summary>
|
||||
/// Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.
|
||||
/// </summary>
|
||||
public class OAuthCreatingTicketContext : BaseContext<OAuthOptions>
|
||||
public class OAuthCreatingTicketContext : BaseContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="OAuthCreatingTicketContext"/>.
|
||||
|
|
@ -46,7 +46,7 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
|||
HttpClient backchannel,
|
||||
OAuthTokenResponse tokens,
|
||||
JObject user)
|
||||
: base(context, options)
|
||||
: base(context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
|
|
@ -76,8 +76,11 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
|||
TokenResponse = tokens;
|
||||
Backchannel = backchannel;
|
||||
User = user;
|
||||
Options = options;
|
||||
}
|
||||
|
||||
public OAuthOptions Options { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the JSON-serialized user or an empty
|
||||
/// <see cref="JObject"/> if it is not available.
|
||||
|
|
|
|||
|
|
@ -9,18 +9,13 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
|||
/// <summary>
|
||||
/// Default <see cref="IOAuthEvents"/> implementation.
|
||||
/// </summary>
|
||||
public class OAuthEvents : IOAuthEvents
|
||||
public class OAuthEvents : RemoteAuthenticationEvents, IOAuthEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the function that is invoked when the CreatingTicket method is invoked.
|
||||
/// </summary>
|
||||
public Func<OAuthCreatingTicketContext, Task> OnCreatingTicket { get; set; } = context => Task.FromResult(0);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the function that is invoked when the SigningIn method is invoked.
|
||||
/// </summary>
|
||||
public Func<SigningInContext, Task> OnSigningIn { get; set; } = context => Task.FromResult(0);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the delegate that is invoked when the RedirectToAuthorizationEndpoint method is invoked.
|
||||
/// </summary>
|
||||
|
|
@ -37,17 +32,10 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
|||
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
|
||||
public virtual Task CreatingTicket(OAuthCreatingTicketContext context) => OnCreatingTicket(context);
|
||||
|
||||
/// <summary>
|
||||
/// Invoked prior to the <see cref="ClaimsIdentity"/> being saved in a local cookie and the browser being redirected to the originally requested URL.
|
||||
/// </summary>
|
||||
/// <param name="context">Contains information about the login session as well as the user <see cref="ClaimsIdentity"/></param>
|
||||
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
|
||||
public virtual Task SigningIn(SigningInContext context) => OnSigningIn(context);
|
||||
|
||||
/// <summary>
|
||||
/// Called when a Challenge causes a redirect to authorize endpoint in the OAuth middleware.
|
||||
/// </summary>
|
||||
/// <param name="context">Contains redirect URI and <see cref="AuthenticationProperties"/> of the challenge.</param>
|
||||
public virtual Task RedirectToAuthorizationEndpoint(OAuthRedirectToAuthorizationContext context) => OnRedirectToAuthorizationEndpoint(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
|||
/// <summary>
|
||||
/// Context passed when a Challenge causes a redirect to authorize endpoint in the middleware.
|
||||
/// </summary>
|
||||
public class OAuthRedirectToAuthorizationContext : BaseContext<OAuthOptions>
|
||||
public class OAuthRedirectToAuthorizationContext : BaseContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new context object.
|
||||
|
|
@ -18,12 +18,15 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
|||
/// <param name="properties">The authentication properties of the challenge.</param>
|
||||
/// <param name="redirectUri">The initial redirect URI.</param>
|
||||
public OAuthRedirectToAuthorizationContext(HttpContext context, OAuthOptions options, AuthenticationProperties properties, string redirectUri)
|
||||
: base(context, options)
|
||||
: base(context)
|
||||
{
|
||||
RedirectUri = redirectUri;
|
||||
Properties = properties;
|
||||
Options = options;
|
||||
}
|
||||
|
||||
public OAuthOptions Options { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URI used for the redirect operation.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -12,14 +12,13 @@ using Microsoft.AspNet.Http;
|
|||
using Microsoft.AspNet.Http.Authentication;
|
||||
using Microsoft.AspNet.Http.Extensions;
|
||||
using Microsoft.AspNet.Http.Features.Authentication;
|
||||
using Microsoft.AspNet.WebUtilities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication.OAuth
|
||||
{
|
||||
public class OAuthHandler<TOptions> : AuthenticationHandler<TOptions> where TOptions : OAuthOptions
|
||||
public class OAuthHandler<TOptions> : RemoteAuthenticationHandler<TOptions> where TOptions : OAuthOptions
|
||||
{
|
||||
private static readonly RandomNumberGenerator CryptoRandom = RandomNumberGenerator.Create();
|
||||
|
||||
|
|
@ -30,131 +29,71 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
|||
|
||||
protected HttpClient Backchannel { get; private set; }
|
||||
|
||||
public override async Task<bool> InvokeAsync()
|
||||
{
|
||||
if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path)
|
||||
{
|
||||
return await InvokeReturnPathAsync();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> InvokeReturnPathAsync()
|
||||
{
|
||||
var ticket = await HandleAuthenticateOnceAsync();
|
||||
if (ticket == null)
|
||||
{
|
||||
Logger.LogWarning("Invalid return state, unable to redirect.");
|
||||
Response.StatusCode = 500;
|
||||
return true;
|
||||
}
|
||||
|
||||
var context = new SigningInContext(Context, ticket)
|
||||
{
|
||||
SignInScheme = Options.SignInScheme,
|
||||
RedirectUri = ticket.Properties.RedirectUri,
|
||||
};
|
||||
ticket.Properties.RedirectUri = null;
|
||||
|
||||
await Options.Events.SigningIn(context);
|
||||
|
||||
if (context.SignInScheme != null && context.Principal != null)
|
||||
{
|
||||
await Context.Authentication.SignInAsync(context.SignInScheme, context.Principal, context.Properties);
|
||||
}
|
||||
|
||||
if (!context.IsRequestCompleted && context.RedirectUri != null)
|
||||
{
|
||||
if (context.Principal == null)
|
||||
{
|
||||
// add a redirect hint that sign-in failed in some way
|
||||
context.RedirectUri = QueryHelpers.AddQueryString(context.RedirectUri, "error", "access_denied");
|
||||
}
|
||||
Response.Redirect(context.RedirectUri);
|
||||
context.RequestCompleted();
|
||||
}
|
||||
|
||||
return context.IsRequestCompleted;
|
||||
}
|
||||
|
||||
protected override async Task<AuthenticationTicket> HandleAuthenticateAsync()
|
||||
protected override async Task<AuthenticateResult> HandleRemoteAuthenticateAsync()
|
||||
{
|
||||
AuthenticationProperties properties = null;
|
||||
try
|
||||
var query = Request.Query;
|
||||
|
||||
var error = query["error"];
|
||||
if (!StringValues.IsNullOrEmpty(error))
|
||||
{
|
||||
var query = Request.Query;
|
||||
return AuthenticateResult.Failed(error);
|
||||
}
|
||||
|
||||
// TODO: Is this a standard error returned by servers?
|
||||
var value = query["error"];
|
||||
if (!StringValues.IsNullOrEmpty(value))
|
||||
var code = query["code"];
|
||||
var state = query["state"];
|
||||
|
||||
properties = Options.StateDataFormat.Unprotect(state);
|
||||
if (properties == null)
|
||||
{
|
||||
return AuthenticateResult.Failed("The oauth state was missing or invalid.");
|
||||
}
|
||||
|
||||
// OAuth2 10.12 CSRF
|
||||
if (!ValidateCorrelationId(properties))
|
||||
{
|
||||
return AuthenticateResult.Failed("Correlation failed.");
|
||||
}
|
||||
|
||||
if (StringValues.IsNullOrEmpty(code))
|
||||
{
|
||||
return AuthenticateResult.Failed("Code was not found.");
|
||||
}
|
||||
|
||||
var tokens = await ExchangeCodeAsync(code, BuildRedirectUri(Options.CallbackPath));
|
||||
|
||||
if (string.IsNullOrEmpty(tokens.AccessToken))
|
||||
{
|
||||
return AuthenticateResult.Failed("Access token was not found.");
|
||||
}
|
||||
|
||||
var identity = new ClaimsIdentity(Options.ClaimsIssuer);
|
||||
|
||||
if (Options.SaveTokensAsClaims)
|
||||
{
|
||||
identity.AddClaim(new Claim("access_token", tokens.AccessToken,
|
||||
ClaimValueTypes.String, Options.ClaimsIssuer));
|
||||
|
||||
if (!string.IsNullOrEmpty(tokens.RefreshToken))
|
||||
{
|
||||
Logger.LogVerbose("Remote server returned an error: " + Request.QueryString);
|
||||
// TODO: Fail request rather than passing through?
|
||||
return null;
|
||||
}
|
||||
|
||||
var code = query["code"];
|
||||
var state = query["state"];
|
||||
|
||||
properties = Options.StateDataFormat.Unprotect(state);
|
||||
if (properties == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// OAuth2 10.12 CSRF
|
||||
if (!ValidateCorrelationId(properties))
|
||||
{
|
||||
return new AuthenticationTicket(properties, Options.AuthenticationScheme);
|
||||
}
|
||||
|
||||
if (StringValues.IsNullOrEmpty(code))
|
||||
{
|
||||
// Null if the remote server returns an error.
|
||||
return new AuthenticationTicket(properties, Options.AuthenticationScheme);
|
||||
}
|
||||
|
||||
var tokens = await ExchangeCodeAsync(code, BuildRedirectUri(Options.CallbackPath));
|
||||
|
||||
if (string.IsNullOrEmpty(tokens.AccessToken))
|
||||
{
|
||||
Logger.LogWarning("Access token was not found");
|
||||
return new AuthenticationTicket(properties, Options.AuthenticationScheme);
|
||||
}
|
||||
|
||||
var identity = new ClaimsIdentity(Options.ClaimsIssuer);
|
||||
|
||||
if (Options.SaveTokensAsClaims)
|
||||
{
|
||||
identity.AddClaim(new Claim("access_token", tokens.AccessToken,
|
||||
identity.AddClaim(new Claim("refresh_token", tokens.RefreshToken,
|
||||
ClaimValueTypes.String, Options.ClaimsIssuer));
|
||||
|
||||
if (!string.IsNullOrEmpty(tokens.RefreshToken))
|
||||
{
|
||||
identity.AddClaim(new Claim("refresh_token", tokens.RefreshToken,
|
||||
ClaimValueTypes.String, Options.ClaimsIssuer));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(tokens.TokenType))
|
||||
{
|
||||
identity.AddClaim(new Claim("token_type", tokens.TokenType,
|
||||
ClaimValueTypes.String, Options.ClaimsIssuer));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(tokens.ExpiresIn))
|
||||
{
|
||||
identity.AddClaim(new Claim("expires_in", tokens.ExpiresIn,
|
||||
ClaimValueTypes.String, Options.ClaimsIssuer));
|
||||
}
|
||||
}
|
||||
|
||||
return await CreateTicketAsync(identity, properties, tokens);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Authentication failed", ex);
|
||||
return new AuthenticationTicket(properties, Options.AuthenticationScheme);
|
||||
if (!string.IsNullOrEmpty(tokens.TokenType))
|
||||
{
|
||||
identity.AddClaim(new Claim("token_type", tokens.TokenType,
|
||||
ClaimValueTypes.String, Options.ClaimsIssuer));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(tokens.ExpiresIn))
|
||||
{
|
||||
identity.AddClaim(new Claim("expires_in", tokens.ExpiresIn,
|
||||
ClaimValueTypes.String, Options.ClaimsIssuer));
|
||||
}
|
||||
}
|
||||
|
||||
return AuthenticateResult.Success(await CreateTicketAsync(identity, properties, tokens));
|
||||
}
|
||||
|
||||
protected virtual async Task<OAuthTokenResponse> ExchangeCodeAsync(string code, string redirectUri)
|
||||
|
|
@ -176,7 +115,6 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
|||
var response = await Backchannel.SendAsync(requestMessage, Context.RequestAborted);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
|
||||
|
||||
return new OAuthTokenResponse(payload);
|
||||
}
|
||||
|
||||
|
|
@ -215,7 +153,6 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
|||
GenerateCorrelationId(properties);
|
||||
|
||||
var authorizationEndpoint = BuildChallengeUrl(properties, BuildRedirectUri(Options.CallbackPath));
|
||||
|
||||
var redirectContext = new OAuthRedirectToAuthorizationContext(
|
||||
Context, Options,
|
||||
properties, authorizationEndpoint);
|
||||
|
|
@ -223,21 +160,6 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
|||
return true;
|
||||
}
|
||||
|
||||
protected override Task HandleSignOutAsync(SignOutContext context)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
protected override Task HandleSignInAsync(SignInContext context)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
protected override Task<bool> HandleForbiddenAsync(ChallengeContext context)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
protected virtual string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri)
|
||||
{
|
||||
var scope = FormatScope();
|
||||
|
|
|
|||
|
|
@ -91,6 +91,11 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
|||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.TokenEndpoint)));
|
||||
}
|
||||
|
||||
if (!Options.CallbackPath.HasValue)
|
||||
{
|
||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.CallbackPath)));
|
||||
}
|
||||
|
||||
if (Options.Events == null)
|
||||
{
|
||||
Options.Events = new OAuthEvents();
|
||||
|
|
@ -112,6 +117,10 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
|||
{
|
||||
Options.SignInScheme = sharedOptions.Value.SignInScheme;
|
||||
}
|
||||
if (string.IsNullOrEmpty(Options.SignInScheme))
|
||||
{
|
||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.SignInScheme)));
|
||||
}
|
||||
}
|
||||
|
||||
protected HttpClient Backchannel { get; private set; }
|
||||
|
|
|
|||
|
|
@ -12,8 +12,13 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
|||
/// <summary>
|
||||
/// Configuration options for <see cref="OAuthMiddleware"/>.
|
||||
/// </summary>
|
||||
public class OAuthOptions : AuthenticationOptions
|
||||
public class OAuthOptions : RemoteAuthenticationOptions
|
||||
{
|
||||
public OAuthOptions()
|
||||
{
|
||||
Events = new OAuthEvents();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the provider-assigned client id.
|
||||
/// </summary>
|
||||
|
|
@ -41,54 +46,20 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
|||
/// </summary>
|
||||
public string UserInformationEndpoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get or sets the text that the user can display on a sign in user interface.
|
||||
/// </summary>
|
||||
public string DisplayName
|
||||
{
|
||||
get { return Description.DisplayName; }
|
||||
set { Description.DisplayName = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets timeout value in milliseconds for back channel communications with the auth provider.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The back channel timeout.
|
||||
/// </value>
|
||||
public TimeSpan BackchannelTimeout { get; set; } = TimeSpan.FromSeconds(60);
|
||||
|
||||
/// <summary>
|
||||
/// The HttpMessageHandler used to communicate with the auth provider.
|
||||
/// This cannot be set at the same time as BackchannelCertificateValidator unless the value
|
||||
/// can be downcast to a WebRequestHandler.
|
||||
/// </summary>
|
||||
public HttpMessageHandler BackchannelHttpHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="IOAuthEvents"/> used to handle authentication events.
|
||||
/// </summary>
|
||||
public IOAuthEvents Events { get; set; } = new OAuthEvents();
|
||||
public new IOAuthEvents Events
|
||||
{
|
||||
get { return (IOAuthEvents)base.Events; }
|
||||
set { base.Events = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A list of permissions to request.
|
||||
/// </summary>
|
||||
public IList<string> Scope { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// The request path within the application's base path where the user-agent will be returned.
|
||||
/// The middleware will process this request when it arrives.
|
||||
/// </summary>
|
||||
public PathString CallbackPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication scheme corresponding to the middleware
|
||||
/// responsible of persisting user's identity after a successful authentication.
|
||||
/// This value typically corresponds to a cookie middleware registered in the Startup class.
|
||||
/// When omitted, <see cref="SharedAuthenticationOptions.SignInScheme"/> is used as a fallback value.
|
||||
/// </summary>
|
||||
public string SignInScheme { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type used to secure data handled by the middleware.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
||||
{
|
||||
public class AuthenticationCompletedContext : BaseControlContext<OpenIdConnectOptions>
|
||||
{
|
||||
public AuthenticationCompletedContext(HttpContext context, OpenIdConnectOptions options)
|
||||
: base(context, options)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
|||
|
||||
namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
||||
{
|
||||
public class AuthenticationFailedContext : BaseControlContext<OpenIdConnectOptions>
|
||||
public class AuthenticationFailedContext : BaseOpenIdConnectContext
|
||||
{
|
||||
public AuthenticationFailedContext(HttpContext context, OpenIdConnectOptions options)
|
||||
: base(context, options)
|
||||
|
|
@ -15,7 +15,5 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
}
|
||||
|
||||
public Exception Exception { get; set; }
|
||||
|
||||
public OpenIdConnectMessage ProtocolMessage { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -6,15 +6,13 @@ using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
|||
|
||||
namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
||||
{
|
||||
public class AuthenticationValidatedContext : BaseControlContext<OpenIdConnectOptions>
|
||||
public class AuthenticationValidatedContext : BaseOpenIdConnectContext
|
||||
{
|
||||
public AuthenticationValidatedContext(HttpContext context, OpenIdConnectOptions options)
|
||||
: base(context, options)
|
||||
{
|
||||
}
|
||||
|
||||
public OpenIdConnectMessage ProtocolMessage { get; set; }
|
||||
|
||||
public OpenIdConnectTokenEndpointResponse TokenEndpointResponse { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
/// <summary>
|
||||
/// This Context can be used to be informed when an 'AuthorizationCode' is received over the OpenIdConnect protocol.
|
||||
/// </summary>
|
||||
public class AuthorizationCodeReceivedContext : BaseControlContext<OpenIdConnectOptions>
|
||||
public class AuthorizationCodeReceivedContext : BaseOpenIdConnectContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a <see cref="AuthorizationCodeReceivedContext"/>
|
||||
|
|
@ -31,11 +31,6 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
/// </summary>
|
||||
public JwtSecurityToken JwtSecurityToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="OpenIdConnectMessage"/>.
|
||||
/// </summary>
|
||||
public OpenIdConnectMessage ProtocolMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the 'redirect_uri'.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -7,15 +7,13 @@ using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
|||
|
||||
namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
||||
{
|
||||
public class AuthorizationResponseReceivedContext : BaseControlContext<OpenIdConnectOptions>
|
||||
public class AuthorizationResponseReceivedContext : BaseOpenIdConnectContext
|
||||
{
|
||||
public AuthorizationResponseReceivedContext(HttpContext context, OpenIdConnectOptions options)
|
||||
: base(context, options)
|
||||
{
|
||||
}
|
||||
|
||||
public OpenIdConnectMessage ProtocolMessage { get; set; }
|
||||
|
||||
public AuthenticationProperties Properties { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
||||
{
|
||||
public class BaseOpenIdConnectContext : BaseControlContext
|
||||
{
|
||||
public BaseOpenIdConnectContext(HttpContext context, OpenIdConnectOptions options)
|
||||
: base(context)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
Options = options;
|
||||
}
|
||||
|
||||
public OpenIdConnectOptions Options { get; }
|
||||
|
||||
public OpenIdConnectMessage ProtocolMessage { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -8,13 +8,8 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
/// <summary>
|
||||
/// Specifies events which the <see cref="OpenIdConnectMiddleware" />invokes to enable developer control over the authentication process.
|
||||
/// </summary>
|
||||
public interface IOpenIdConnectEvents
|
||||
public interface IOpenIdConnectEvents : IRemoteAuthenticationEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoked when the authentication process completes.
|
||||
/// </summary>
|
||||
Task AuthenticationCompleted(AuthenticationCompletedContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -6,15 +6,13 @@ using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
|||
|
||||
namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
||||
{
|
||||
public class MessageReceivedContext : BaseControlContext<OpenIdConnectOptions>
|
||||
public class MessageReceivedContext : BaseOpenIdConnectContext
|
||||
{
|
||||
public MessageReceivedContext(HttpContext context, OpenIdConnectOptions options)
|
||||
: base(context, options)
|
||||
{
|
||||
}
|
||||
|
||||
public OpenIdConnectMessage ProtocolMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Bearer Token. This will give application an opportunity to retrieve token from an alternation location.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -9,13 +9,8 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
/// <summary>
|
||||
/// Specifies events which the <see cref="OpenIdConnectMiddleware" />invokes to enable developer control over the authentication process.
|
||||
/// </summary>
|
||||
public class OpenIdConnectEvents : IOpenIdConnectEvents
|
||||
public class OpenIdConnectEvents : RemoteAuthenticationEvents, IOpenIdConnectEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoked when the authentication process completes.
|
||||
/// </summary>
|
||||
public Func<AuthenticationCompletedContext, Task> OnAuthenticationCompleted { get; set; } = context => Task.FromResult(0);
|
||||
|
||||
/// <summary>
|
||||
/// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed.
|
||||
/// </summary>
|
||||
|
|
@ -61,8 +56,6 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
/// </summary>
|
||||
public Func<UserInformationReceivedContext, Task> OnUserInformationReceived { get; set; } = context => Task.FromResult(0);
|
||||
|
||||
public virtual Task AuthenticationCompleted(AuthenticationCompletedContext context) => OnAuthenticationCompleted(context);
|
||||
|
||||
public virtual Task AuthenticationFailed(AuthenticationFailedContext context) => OnAuthenticationFailed(context);
|
||||
|
||||
public virtual Task AuthenticationValidated(AuthenticationValidatedContext context) => OnAuthenticationValidated(context);
|
||||
|
|
|
|||
|
|
@ -10,16 +10,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
/// When a user configures the <see cref="OpenIdConnectMiddleware"/> to be notified prior to redirecting to an IdentityProvider
|
||||
/// an instance of <see cref="RedirectContext"/> is passed to the 'RedirectToAuthenticationEndpoint' or 'RedirectToEndSessionEndpoint' events.
|
||||
/// </summary>
|
||||
public class RedirectContext : BaseControlContext<OpenIdConnectOptions>
|
||||
public class RedirectContext : BaseOpenIdConnectContext
|
||||
{
|
||||
public RedirectContext(HttpContext context, OpenIdConnectOptions options)
|
||||
: base(context, options)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="OpenIdConnectMessage"/>.
|
||||
/// </summary>
|
||||
public OpenIdConnectMessage ProtocolMessage { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
/// <summary>
|
||||
/// This Context can be used to be informed when an 'AuthorizationCode' is redeemed for tokens at the token endpoint.
|
||||
/// </summary>
|
||||
public class TokenResponseReceivedContext : BaseControlContext<OpenIdConnectOptions>
|
||||
public class TokenResponseReceivedContext : BaseOpenIdConnectContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a <see cref="TokenResponseReceivedContext"/>
|
||||
|
|
@ -20,11 +20,5 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
/// Gets or sets the <see cref="OpenIdConnectTokenEndpointResponse"/> that contains the tokens and json response received after redeeming the code at the token endpoint.
|
||||
/// </summary>
|
||||
public OpenIdConnectTokenEndpointResponse TokenEndpointResponse { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="OpenIdConnectMessage"/>.
|
||||
/// </summary>
|
||||
public OpenIdConnectMessage ProtocolMessage { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,15 +7,13 @@ using Newtonsoft.Json.Linq;
|
|||
|
||||
namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
||||
{
|
||||
public class UserInformationReceivedContext : BaseControlContext<OpenIdConnectOptions>
|
||||
public class UserInformationReceivedContext : BaseOpenIdConnectContext
|
||||
{
|
||||
public UserInformationReceivedContext(HttpContext context, OpenIdConnectOptions options)
|
||||
: base(context, options)
|
||||
{
|
||||
}
|
||||
|
||||
public OpenIdConnectMessage ProtocolMessage { get; set; }
|
||||
|
||||
public JObject User { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
/// <summary>
|
||||
/// A per-request authentication handler for the OpenIdConnectAuthenticationMiddleware.
|
||||
/// </summary>
|
||||
public class OpenIdConnectHandler : AuthenticationHandler<OpenIdConnectOptions>
|
||||
public class OpenIdConnectHandler : RemoteAuthenticationHandler<OpenIdConnectOptions>
|
||||
{
|
||||
private const string NonceProperty = "N";
|
||||
private const string UriSchemeDelimiter = "://";
|
||||
|
|
@ -263,7 +263,6 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
}
|
||||
|
||||
Response.Redirect(redirectUri);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (Options.AuthenticationMethod == OpenIdConnectRedirectBehavior.FormPost)
|
||||
|
|
@ -292,12 +291,10 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
Response.Headers[HeaderNames.Expires] = "-1";
|
||||
|
||||
await Response.Body.WriteAsync(buffer, 0, buffer.Length);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Logger.LogError("An unsupported authentication method has been configured: {0}", Options.AuthenticationMethod);
|
||||
return false;
|
||||
throw new NotImplementedException($"An unsupported authentication method has been configured: {Options.AuthenticationMethod}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -305,16 +302,10 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
/// </summary>
|
||||
/// <returns>An <see cref="AuthenticationTicket"/> if successful.</returns>
|
||||
/// <remarks>Uses log id's OIDCH-0000 - OIDCH-0025</remarks>
|
||||
protected override async Task<AuthenticationTicket> HandleAuthenticateAsync()
|
||||
protected override async Task<AuthenticateResult> HandleRemoteAuthenticateAsync()
|
||||
{
|
||||
Logger.LogDebug(Resources.OIDCH_0000_AuthenticateCoreAsync, this.GetType());
|
||||
|
||||
// Allow login to be constrained to a specific path. Need to make this runtime configurable.
|
||||
if (Options.CallbackPath.HasValue && Options.CallbackPath != (Request.PathBase + Request.Path))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
OpenIdConnectMessage message = null;
|
||||
|
||||
if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
|
||||
|
|
@ -326,9 +317,8 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
// See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Security
|
||||
if (!string.IsNullOrEmpty(message.IdToken) || !string.IsNullOrEmpty(message.AccessToken))
|
||||
{
|
||||
Logger.LogError("An OpenID Connect response cannot contain an identity token " +
|
||||
"or an access token when using response_mode=query");
|
||||
return null;
|
||||
return AuthenticateResult.Failed("An OpenID Connect response cannot contain an " +
|
||||
"identity token or an access token when using response_mode=query");
|
||||
}
|
||||
}
|
||||
// assumption: if the ContentType is "application/x-www-form-urlencoded" it should be safe to read as it is small.
|
||||
|
|
@ -344,7 +334,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
|
||||
if (message == null)
|
||||
{
|
||||
return null;
|
||||
return AuthenticateResult.Failed("No message.");
|
||||
}
|
||||
|
||||
try
|
||||
|
|
@ -352,11 +342,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
var messageReceivedContext = await RunMessageReceivedEventAsync(message);
|
||||
if (messageReceivedContext.HandledResponse)
|
||||
{
|
||||
return messageReceivedContext.AuthenticationTicket;
|
||||
return AuthenticateResult.Success(messageReceivedContext.AuthenticationTicket);
|
||||
}
|
||||
else if (messageReceivedContext.Skipped)
|
||||
{
|
||||
return null;
|
||||
return AuthenticateResult.Success(ticket: null);
|
||||
}
|
||||
message = messageReceivedContext.ProtocolMessage;
|
||||
|
||||
|
|
@ -365,7 +355,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
{
|
||||
// This wasn't a valid ODIC message, it may not have been intended for us.
|
||||
Logger.LogVerbose(Resources.OIDCH_0004_MessageStateIsNullOrEmpty);
|
||||
return null;
|
||||
return AuthenticateResult.Failed(Resources.OIDCH_0004_MessageStateIsNullOrEmpty);
|
||||
}
|
||||
|
||||
// if state exists and we failed to 'unprotect' this is not a message we should process.
|
||||
|
|
@ -373,24 +363,24 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
if (properties == null)
|
||||
{
|
||||
Logger.LogError(Resources.OIDCH_0005_MessageStateIsInvalid);
|
||||
return null;
|
||||
return AuthenticateResult.Failed(Resources.OIDCH_0005_MessageStateIsInvalid);
|
||||
}
|
||||
|
||||
// if any of the error fields are set, throw error null
|
||||
if (!string.IsNullOrEmpty(message.Error))
|
||||
{
|
||||
// REVIEW: this error formatting is pretty nuts
|
||||
Logger.LogError(Resources.OIDCH_0006_MessageContainsError, message.Error, message.ErrorDescription ?? "ErrorDecription null", message.ErrorUri ?? "ErrorUri null");
|
||||
throw new OpenIdConnectProtocolException(string.Format(CultureInfo.InvariantCulture, Resources.OIDCH_0006_MessageContainsError, message.Error, message.ErrorDescription ?? "ErrorDecription null", message.ErrorUri ?? "ErrorUri null"));
|
||||
return AuthenticateResult.Failed(new OpenIdConnectProtocolException(string.Format(CultureInfo.InvariantCulture, Resources.OIDCH_0006_MessageContainsError, message.Error, message.ErrorDescription ?? "ErrorDecription null", message.ErrorUri ?? "ErrorUri null")));
|
||||
}
|
||||
|
||||
string userstate = null;
|
||||
properties.Items.TryGetValue(OpenIdConnectDefaults.UserstatePropertiesKey, out userstate);
|
||||
message.State = userstate;
|
||||
|
||||
|
||||
if (!ValidateCorrelationId(properties))
|
||||
{
|
||||
return null;
|
||||
return AuthenticateResult.Failed("Correlation failed.");
|
||||
}
|
||||
|
||||
if (_configuration == null && Options.ConfigurationManager != null)
|
||||
|
|
@ -409,12 +399,12 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
if (authorizationResponseReceivedContext.HandledResponse)
|
||||
{
|
||||
Logger.LogVerbose("AuthorizationResponseReceived.HandledResponse");
|
||||
return authorizationResponseReceivedContext.AuthenticationTicket;
|
||||
return AuthenticateResult.Success(authorizationResponseReceivedContext.AuthenticationTicket);
|
||||
}
|
||||
else if (authorizationResponseReceivedContext.Skipped)
|
||||
{
|
||||
Logger.LogVerbose("AuthorizationResponseReceived.Skipped");
|
||||
return null;
|
||||
return AuthenticateResult.Success(ticket: null);
|
||||
}
|
||||
message = authorizationResponseReceivedContext.ProtocolMessage;
|
||||
properties = authorizationResponseReceivedContext.Properties;
|
||||
|
|
@ -430,7 +420,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
else
|
||||
{
|
||||
Logger.LogDebug(Resources.OIDCH_0045_Id_Token_Code_Missing);
|
||||
return null;
|
||||
return AuthenticateResult.Failed(Resources.OIDCH_0045_Id_Token_Code_Missing);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
|
|
@ -450,11 +440,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
var authenticationFailedContext = await RunAuthenticationFailedEventAsync(message, exception);
|
||||
if (authenticationFailedContext.HandledResponse)
|
||||
{
|
||||
return authenticationFailedContext.AuthenticationTicket;
|
||||
return AuthenticateResult.Success(authenticationFailedContext.AuthenticationTicket);
|
||||
}
|
||||
else if (authenticationFailedContext.Skipped)
|
||||
{
|
||||
return null;
|
||||
return AuthenticateResult.Success(ticket: null);
|
||||
}
|
||||
|
||||
throw;
|
||||
|
|
@ -462,7 +452,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
}
|
||||
|
||||
// Authorization Code Flow
|
||||
private async Task<AuthenticationTicket> HandleCodeOnlyFlow(OpenIdConnectMessage message, AuthenticationProperties properties)
|
||||
private async Task<AuthenticateResult> HandleCodeOnlyFlow(OpenIdConnectMessage message, AuthenticationProperties properties)
|
||||
{
|
||||
AuthenticationTicket ticket = null;
|
||||
JwtSecurityToken jwt = null;
|
||||
|
|
@ -476,11 +466,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
var authorizationCodeReceivedContext = await RunAuthorizationCodeReceivedEventAsync(message, properties, ticket, jwt);
|
||||
if (authorizationCodeReceivedContext.HandledResponse)
|
||||
{
|
||||
return authorizationCodeReceivedContext.AuthenticationTicket;
|
||||
return AuthenticateResult.Success(authorizationCodeReceivedContext.AuthenticationTicket);
|
||||
}
|
||||
else if (authorizationCodeReceivedContext.Skipped)
|
||||
{
|
||||
return null;
|
||||
return AuthenticateResult.Success(ticket: null);
|
||||
}
|
||||
message = authorizationCodeReceivedContext.ProtocolMessage;
|
||||
var code = authorizationCodeReceivedContext.Code;
|
||||
|
|
@ -493,11 +483,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
var authorizationCodeRedeemedContext = await RunTokenResponseReceivedEventAsync(message, tokenEndpointResponse);
|
||||
if (authorizationCodeRedeemedContext.HandledResponse)
|
||||
{
|
||||
return authorizationCodeRedeemedContext.AuthenticationTicket;
|
||||
return AuthenticateResult.Success(authorizationCodeRedeemedContext.AuthenticationTicket);
|
||||
}
|
||||
else if (authorizationCodeRedeemedContext.Skipped)
|
||||
{
|
||||
return null;
|
||||
return AuthenticateResult.Success(ticket: null);
|
||||
}
|
||||
|
||||
message = authorizationCodeRedeemedContext.ProtocolMessage;
|
||||
|
|
@ -526,11 +516,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
var authenticationValidatedContext = await RunAuthenticationValidatedEventAsync(message, ticket, tokenEndpointResponse);
|
||||
if (authenticationValidatedContext.HandledResponse)
|
||||
{
|
||||
return authenticationValidatedContext.AuthenticationTicket;
|
||||
return AuthenticateResult.Success(authenticationValidatedContext.AuthenticationTicket);
|
||||
}
|
||||
else if (authenticationValidatedContext.Skipped)
|
||||
{
|
||||
return null;
|
||||
return AuthenticateResult.Success(ticket: null);
|
||||
}
|
||||
ticket = authenticationValidatedContext.AuthenticationTicket;
|
||||
|
||||
|
|
@ -546,11 +536,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
ticket = await GetUserInformationAsync(tokenEndpointResponse.ProtocolMessage, jwt, ticket);
|
||||
}
|
||||
|
||||
return ticket;
|
||||
return AuthenticateResult.Success(ticket);
|
||||
}
|
||||
|
||||
// Implicit Flow or Hybrid Flow
|
||||
private async Task<AuthenticationTicket> HandleIdTokenFlows(OpenIdConnectMessage message, AuthenticationProperties properties)
|
||||
private async Task<AuthenticateResult> HandleIdTokenFlows(OpenIdConnectMessage message, AuthenticationProperties properties)
|
||||
{
|
||||
Logger.LogDebug(Resources.OIDCH_0020_IdTokenReceived, message.IdToken);
|
||||
|
||||
|
|
@ -575,11 +565,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
var authenticationValidatedContext = await RunAuthenticationValidatedEventAsync(message, ticket, tokenEndpointResponse: null);
|
||||
if (authenticationValidatedContext.HandledResponse)
|
||||
{
|
||||
return authenticationValidatedContext.AuthenticationTicket;
|
||||
return AuthenticateResult.Success(authenticationValidatedContext.AuthenticationTicket);
|
||||
}
|
||||
else if (authenticationValidatedContext.Skipped)
|
||||
{
|
||||
return null;
|
||||
return AuthenticateResult.Success(ticket: null);
|
||||
}
|
||||
message = authenticationValidatedContext.ProtocolMessage;
|
||||
ticket = authenticationValidatedContext.AuthenticationTicket;
|
||||
|
|
@ -590,11 +580,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
var authorizationCodeReceivedContext = await RunAuthorizationCodeReceivedEventAsync(message, properties, ticket, jwt);
|
||||
if (authorizationCodeReceivedContext.HandledResponse)
|
||||
{
|
||||
return authorizationCodeReceivedContext.AuthenticationTicket;
|
||||
return AuthenticateResult.Success(authorizationCodeReceivedContext.AuthenticationTicket);
|
||||
}
|
||||
else if (authorizationCodeReceivedContext.Skipped)
|
||||
{
|
||||
return null;
|
||||
return AuthenticateResult.Success(ticket: null);
|
||||
}
|
||||
message = authorizationCodeReceivedContext.ProtocolMessage;
|
||||
ticket = authorizationCodeReceivedContext.AuthenticationTicket;
|
||||
|
|
@ -619,7 +609,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
}
|
||||
}
|
||||
|
||||
return ticket;
|
||||
return AuthenticateResult.Success(ticket);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -1135,54 +1125,5 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
|
||||
return ticket;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls InvokeReplyPathAsync
|
||||
/// </summary>
|
||||
/// <returns>True if the request was handled, false if the next middleware should be invoked.</returns>
|
||||
public override Task<bool> InvokeAsync()
|
||||
{
|
||||
return InvokeReturnPathAsync();
|
||||
}
|
||||
|
||||
private async Task<bool> InvokeReturnPathAsync()
|
||||
{
|
||||
var ticket = await HandleAuthenticateOnceAsync();
|
||||
if (ticket != null)
|
||||
{
|
||||
Logger.LogDebug("Authentication completed.");
|
||||
|
||||
var authenticationCompletedContext = new AuthenticationCompletedContext(Context, Options)
|
||||
{
|
||||
AuthenticationTicket = ticket,
|
||||
};
|
||||
await Options.Events.AuthenticationCompleted(authenticationCompletedContext);
|
||||
if (authenticationCompletedContext.HandledResponse)
|
||||
{
|
||||
Logger.LogVerbose("The AuthenticationCompleted event returned Handled.");
|
||||
return true;
|
||||
}
|
||||
else if (authenticationCompletedContext.Skipped)
|
||||
{
|
||||
Logger.LogVerbose("The AuthenticationCompleted event returned Skipped.");
|
||||
return false;
|
||||
}
|
||||
ticket = authenticationCompletedContext.AuthenticationTicket;
|
||||
|
||||
if (ticket.Principal != null)
|
||||
{
|
||||
await Request.HttpContext.Authentication.SignInAsync(Options.SignInScheme, ticket.Principal, ticket.Properties);
|
||||
}
|
||||
|
||||
// Redirect back to the original secured resource, if any.
|
||||
if (!string.IsNullOrEmpty(ticket.Properties.RedirectUri))
|
||||
{
|
||||
Response.Redirect(ticket.Properties.RedirectUri);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,10 +77,19 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(Options.SignInScheme) && !string.IsNullOrEmpty(sharedOptions.Value.SignInScheme))
|
||||
if (!Options.CallbackPath.HasValue)
|
||||
{
|
||||
throw new ArgumentException("Options.CallbackPath must be provided.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(Options.SignInScheme))
|
||||
{
|
||||
Options.SignInScheme = sharedOptions.Value.SignInScheme;
|
||||
}
|
||||
if (string.IsNullOrEmpty(Options.SignInScheme))
|
||||
{
|
||||
throw new ArgumentException("Options.SignInScheme is required.");
|
||||
}
|
||||
|
||||
if (Options.HtmlEncoder == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IdentityModel.Tokens;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Net.Http;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Authentication;
|
||||
using Microsoft.Extensions.WebEncoders;
|
||||
|
|
@ -19,7 +17,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
/// <summary>
|
||||
/// Configuration options for <see cref="OpenIdConnectOptions"/>
|
||||
/// </summary>
|
||||
public class OpenIdConnectOptions : AuthenticationOptions
|
||||
public class OpenIdConnectOptions : RemoteAuthenticationOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="OpenIdConnectOptions"/>
|
||||
|
|
@ -50,6 +48,8 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
{
|
||||
AuthenticationScheme = authenticationScheme;
|
||||
DisplayName = OpenIdConnectDefaults.Caption;
|
||||
CallbackPath = new PathString("/signin-oidc");
|
||||
Events = new OpenIdConnectEvents();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -65,36 +65,6 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
/// </summary>
|
||||
public string Authority { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The HttpMessageHandler used to retrieve metadata.
|
||||
/// This cannot be set at the same time as BackchannelCertificateValidator unless the value
|
||||
/// is a WebRequestHandler.
|
||||
/// </summary>
|
||||
public HttpMessageHandler BackchannelHttpHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timeout when using the backchannel to make an http call.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "By design we use the property name in the exception")]
|
||||
public TimeSpan BackchannelTimeout { get; set; } = TimeSpan.FromSeconds(60);
|
||||
|
||||
/// <summary>
|
||||
/// Get or sets the text that the user can display on a sign in user interface.
|
||||
/// </summary>
|
||||
public string DisplayName
|
||||
{
|
||||
get { return Description.DisplayName; }
|
||||
set { Description.DisplayName = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An optional constrained path on which to process the authentication callback.
|
||||
/// If not provided and RedirectUri is available, this value will be generated from RedirectUri.
|
||||
/// </summary>
|
||||
/// <remarks>If you set this value, then the <see cref="OpenIdConnectHandler"/> will only listen for posts at this address.
|
||||
/// If the IdentityProvider does not post to this address, you may end up in a 401 -> IdentityProvider -> Client -> 401 -> ...</remarks>
|
||||
public PathString CallbackPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the 'client_id'.
|
||||
/// </summary>
|
||||
|
|
@ -136,7 +106,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
/// <summary>
|
||||
/// Gets or sets the <see cref="IOpenIdConnectEvents"/> to notify when processing OpenIdConnect messages.
|
||||
/// </summary>
|
||||
public IOpenIdConnectEvents Events { get; set; } = new OpenIdConnectEvents();
|
||||
public new IOpenIdConnectEvents Events
|
||||
{
|
||||
get { return (IOpenIdConnectEvents)base.Events; }
|
||||
set { base.Events = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="OpenIdConnectProtocolValidator"/> that is used to ensure that the 'id_token' received
|
||||
|
|
@ -194,11 +168,6 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
/// </summary>
|
||||
public IList<string> Scope { get; } = new List<string> { "openid", "profile" };
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SignInScheme which will be used to set the <see cref="ClaimsIdentity.AuthenticationType"/>.
|
||||
/// </summary>
|
||||
public string SignInScheme { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type used to secure data handled by the middleware.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// OIDCH_0029: ChallengeContext == null AND !Options.AutomaticAuthentication
|
||||
/// OIDCH_0029: ChallengeContext == null AND !Options.AutomaticAuthenticate
|
||||
/// </summary>
|
||||
internal static string OIDCH_0029_ChallengContextEqualsNull
|
||||
{
|
||||
|
|
@ -115,7 +115,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// OIDCH_0029: ChallengeContext == null AND !Options.AutomaticAuthentication
|
||||
/// OIDCH_0029: ChallengeContext == null AND !Options.AutomaticAuthenticate
|
||||
/// </summary>
|
||||
internal static string FormatOIDCH_0029_ChallengContextEqualsNull()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@
|
|||
<value>OIDCH_0028: Response.StatusCode != 401, StatusCode: '{0}'.</value>
|
||||
</data>
|
||||
<data name="OIDCH_0029_ChallengContextEqualsNull" xml:space="preserve">
|
||||
<value>OIDCH_0029: ChallengeContext == null AND !Options.AutomaticAuthentication</value>
|
||||
<value>OIDCH_0029: ChallengeContext == null AND !Options.AutomaticAuthenticate</value>
|
||||
</data>
|
||||
<data name="OIDCH_0030_Using_Properties_RedirectUri" xml:space="preserve">
|
||||
<value>OIDCH_0030: Using properties.RedirectUri for 'local redirect' post authentication: '{0}'.</value>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for other Twitter contexts.
|
||||
/// </summary>
|
||||
public class BaseTwitterContext : BaseContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a <see cref="BaseTwitterContext"/>
|
||||
/// </summary>
|
||||
/// <param name="context">The HTTP environment</param>
|
||||
/// <param name="options">The options for Twitter</param>
|
||||
public BaseTwitterContext(HttpContext context, TwitterOptions options)
|
||||
: base(context)
|
||||
{
|
||||
Options = options;
|
||||
}
|
||||
|
||||
public TwitterOptions Options { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ namespace Microsoft.AspNet.Authentication.Twitter
|
|||
/// <summary>
|
||||
/// Specifies callback methods which the <see cref="TwitterMiddleware"></see> invokes to enable developer control over the authentication process. />
|
||||
/// </summary>
|
||||
public interface ITwitterEvents
|
||||
public interface ITwitterEvents : IRemoteAuthenticationEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoked whenever Twitter succesfully authenticates a user
|
||||
|
|
@ -17,13 +17,6 @@ namespace Microsoft.AspNet.Authentication.Twitter
|
|||
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
|
||||
Task CreatingTicket(TwitterCreatingTicketContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Invoked prior to the <see cref="System.Security.Claims.ClaimsIdentity"/> being saved in a local cookie and the browser being redirected to the originally requested URL.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
|
||||
Task SigningIn(SigningInContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Called when a Challenge causes a redirect to authorize endpoint in the Twitter middleware
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ namespace Microsoft.AspNet.Authentication.Twitter
|
|||
/// <summary>
|
||||
/// Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.
|
||||
/// </summary>
|
||||
public class TwitterCreatingTicketContext : BaseContext
|
||||
public class TwitterCreatingTicketContext : BaseTwitterContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a <see cref="TwitterCreatingTicketContext"/>
|
||||
|
|
@ -22,11 +22,12 @@ namespace Microsoft.AspNet.Authentication.Twitter
|
|||
/// <param name="accessTokenSecret">Twitter access token secret</param>
|
||||
public TwitterCreatingTicketContext(
|
||||
HttpContext context,
|
||||
TwitterOptions options,
|
||||
string userId,
|
||||
string screenName,
|
||||
string accessToken,
|
||||
string accessTokenSecret)
|
||||
: base(context)
|
||||
: base(context, options)
|
||||
{
|
||||
UserId = userId;
|
||||
ScreenName = screenName;
|
||||
|
|
|
|||
|
|
@ -9,18 +9,13 @@ namespace Microsoft.AspNet.Authentication.Twitter
|
|||
/// <summary>
|
||||
/// Default <see cref="ITwitterEvents"/> implementation.
|
||||
/// </summary>
|
||||
public class TwitterEvents : ITwitterEvents
|
||||
public class TwitterEvents : RemoteAuthenticationEvents, ITwitterEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the function that is invoked when the Authenticated method is invoked.
|
||||
/// </summary>
|
||||
public Func<TwitterCreatingTicketContext, Task> OnCreatingTicket { get; set; } = context => Task.FromResult(0);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the function that is invoked when the ReturnEndpoint method is invoked.
|
||||
/// </summary>
|
||||
public Func<SigningInContext, Task> OnSigningIn { get; set; } = context => Task.FromResult(0);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the delegate that is invoked when the ApplyRedirect method is invoked.
|
||||
/// </summary>
|
||||
|
|
@ -37,13 +32,6 @@ namespace Microsoft.AspNet.Authentication.Twitter
|
|||
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
|
||||
public virtual Task CreatingTicket(TwitterCreatingTicketContext context) => OnCreatingTicket(context);
|
||||
|
||||
/// <summary>
|
||||
/// Invoked prior to the <see cref="System.Security.Claims.ClaimsIdentity"/> being saved in a local cookie and the browser being redirected to the originally requested URL.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
|
||||
public virtual Task SigningIn(SigningInContext context) => OnSigningIn(context);
|
||||
|
||||
/// <summary>
|
||||
/// Called when a Challenge causes a redirect to authorize endpoint in the Twitter middleware
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ namespace Microsoft.AspNet.Authentication.Twitter
|
|||
/// <summary>
|
||||
/// The Context passed when a Challenge causes a redirect to authorize endpoint in the Twitter middleware.
|
||||
/// </summary>
|
||||
public class TwitterRedirectToAuthorizationEndpointContext : BaseContext<TwitterOptions>
|
||||
public class TwitterRedirectToAuthorizationEndpointContext : BaseTwitterContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new context object.
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ using Microsoft.Extensions.Primitives;
|
|||
|
||||
namespace Microsoft.AspNet.Authentication.Twitter
|
||||
{
|
||||
internal class TwitterHandler : AuthenticationHandler<TwitterOptions>
|
||||
internal class TwitterHandler : RemoteAuthenticationHandler<TwitterOptions>
|
||||
{
|
||||
private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
private const string StateCookie = "__TwitterState";
|
||||
|
|
@ -34,89 +34,70 @@ namespace Microsoft.AspNet.Authentication.Twitter
|
|||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public override async Task<bool> InvokeAsync()
|
||||
{
|
||||
if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path)
|
||||
{
|
||||
return await InvokeReturnPathAsync();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override async Task<AuthenticationTicket> HandleAuthenticateAsync()
|
||||
protected override async Task<AuthenticateResult> HandleRemoteAuthenticateAsync()
|
||||
{
|
||||
AuthenticationProperties properties = null;
|
||||
try
|
||||
var query = Request.Query;
|
||||
var protectedRequestToken = Request.Cookies[StateCookie];
|
||||
|
||||
var requestToken = Options.StateDataFormat.Unprotect(protectedRequestToken);
|
||||
|
||||
if (requestToken == null)
|
||||
{
|
||||
var query = Request.Query;
|
||||
var protectedRequestToken = Request.Cookies[StateCookie];
|
||||
|
||||
var requestToken = Options.StateDataFormat.Unprotect(protectedRequestToken);
|
||||
|
||||
if (requestToken == null)
|
||||
{
|
||||
Logger.LogWarning("Invalid state");
|
||||
return null;
|
||||
}
|
||||
|
||||
properties = requestToken.Properties;
|
||||
|
||||
var returnedToken = query["oauth_token"];
|
||||
if (StringValues.IsNullOrEmpty(returnedToken))
|
||||
{
|
||||
Logger.LogWarning("Missing oauth_token");
|
||||
return new AuthenticationTicket(properties, Options.AuthenticationScheme);
|
||||
}
|
||||
|
||||
if (!string.Equals(returnedToken, requestToken.Token, StringComparison.Ordinal))
|
||||
{
|
||||
Logger.LogWarning("Unmatched token");
|
||||
return new AuthenticationTicket(properties, Options.AuthenticationScheme);
|
||||
}
|
||||
|
||||
var oauthVerifier = query["oauth_verifier"];
|
||||
if (StringValues.IsNullOrEmpty(oauthVerifier))
|
||||
{
|
||||
Logger.LogWarning("Missing or blank oauth_verifier");
|
||||
return new AuthenticationTicket(properties, Options.AuthenticationScheme);
|
||||
}
|
||||
|
||||
var cookieOptions = new CookieOptions
|
||||
{
|
||||
HttpOnly = true,
|
||||
Secure = Request.IsHttps
|
||||
};
|
||||
|
||||
Response.Cookies.Delete(StateCookie, cookieOptions);
|
||||
|
||||
var accessToken = await ObtainAccessTokenAsync(Options.ConsumerKey, Options.ConsumerSecret, requestToken, oauthVerifier);
|
||||
|
||||
var identity = new ClaimsIdentity(new[]
|
||||
{
|
||||
new Claim(ClaimTypes.NameIdentifier, accessToken.UserId, ClaimValueTypes.String, Options.ClaimsIssuer),
|
||||
new Claim(ClaimTypes.Name, accessToken.ScreenName, ClaimValueTypes.String, Options.ClaimsIssuer),
|
||||
new Claim("urn:twitter:userid", accessToken.UserId, ClaimValueTypes.String, Options.ClaimsIssuer),
|
||||
new Claim("urn:twitter:screenname", accessToken.ScreenName, ClaimValueTypes.String, Options.ClaimsIssuer)
|
||||
},
|
||||
Options.ClaimsIssuer);
|
||||
|
||||
if (Options.SaveTokensAsClaims)
|
||||
{
|
||||
identity.AddClaim(new Claim("access_token", accessToken.Token, ClaimValueTypes.String, Options.ClaimsIssuer));
|
||||
}
|
||||
|
||||
return await CreateTicketAsync(identity, properties, accessToken);
|
||||
return AuthenticateResult.Failed("Invalid state cookie.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
properties = requestToken.Properties;
|
||||
|
||||
// REVIEW: see which of these are really errors
|
||||
|
||||
var returnedToken = query["oauth_token"];
|
||||
if (StringValues.IsNullOrEmpty(returnedToken))
|
||||
{
|
||||
Logger.LogError("Authentication failed", ex);
|
||||
return new AuthenticationTicket(properties, Options.AuthenticationScheme);
|
||||
return AuthenticateResult.Failed("Missing oauth_token");
|
||||
}
|
||||
|
||||
if (!string.Equals(returnedToken, requestToken.Token, StringComparison.Ordinal))
|
||||
{
|
||||
return AuthenticateResult.Failed("Unmatched token");
|
||||
}
|
||||
|
||||
var oauthVerifier = query["oauth_verifier"];
|
||||
if (StringValues.IsNullOrEmpty(oauthVerifier))
|
||||
{
|
||||
return AuthenticateResult.Failed("Missing or blank oauth_verifier");
|
||||
}
|
||||
|
||||
var cookieOptions = new CookieOptions
|
||||
{
|
||||
HttpOnly = true,
|
||||
Secure = Request.IsHttps
|
||||
};
|
||||
|
||||
Response.Cookies.Delete(StateCookie, cookieOptions);
|
||||
|
||||
var accessToken = await ObtainAccessTokenAsync(Options.ConsumerKey, Options.ConsumerSecret, requestToken, oauthVerifier);
|
||||
|
||||
var identity = new ClaimsIdentity(new[]
|
||||
{
|
||||
new Claim(ClaimTypes.NameIdentifier, accessToken.UserId, ClaimValueTypes.String, Options.ClaimsIssuer),
|
||||
new Claim(ClaimTypes.Name, accessToken.ScreenName, ClaimValueTypes.String, Options.ClaimsIssuer),
|
||||
new Claim("urn:twitter:userid", accessToken.UserId, ClaimValueTypes.String, Options.ClaimsIssuer),
|
||||
new Claim("urn:twitter:screenname", accessToken.ScreenName, ClaimValueTypes.String, Options.ClaimsIssuer)
|
||||
},
|
||||
Options.ClaimsIssuer);
|
||||
|
||||
if (Options.SaveTokensAsClaims)
|
||||
{
|
||||
identity.AddClaim(new Claim("access_token", accessToken.Token, ClaimValueTypes.String, Options.ClaimsIssuer));
|
||||
}
|
||||
|
||||
return AuthenticateResult.Success(await CreateTicketAsync(identity, properties, accessToken));
|
||||
}
|
||||
|
||||
protected virtual async Task<AuthenticationTicket> CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, AccessToken token)
|
||||
{
|
||||
var context = new TwitterCreatingTicketContext(Context, token.UserId, token.ScreenName, token.Token, token.TokenSecret)
|
||||
var context = new TwitterCreatingTicketContext(Context, Options, token.UserId, token.ScreenName, token.Token, token.TokenSecret)
|
||||
{
|
||||
Principal = new ClaimsPrincipal(identity),
|
||||
Properties = properties
|
||||
|
|
@ -145,83 +126,23 @@ namespace Microsoft.AspNet.Authentication.Twitter
|
|||
properties.RedirectUri = CurrentUri;
|
||||
}
|
||||
|
||||
// If CallbackConfirmed is false, this will throw
|
||||
var requestToken = await ObtainRequestTokenAsync(Options.ConsumerKey, Options.ConsumerSecret, BuildRedirectUri(Options.CallbackPath), properties);
|
||||
if (requestToken.CallbackConfirmed)
|
||||
var twitterAuthenticationEndpoint = AuthenticationEndpoint + requestToken.Token;
|
||||
|
||||
var cookieOptions = new CookieOptions
|
||||
{
|
||||
var twitterAuthenticationEndpoint = AuthenticationEndpoint + requestToken.Token;
|
||||
|
||||
var cookieOptions = new CookieOptions
|
||||
{
|
||||
HttpOnly = true,
|
||||
Secure = Request.IsHttps
|
||||
};
|
||||
|
||||
Response.Cookies.Append(StateCookie, Options.StateDataFormat.Protect(requestToken), cookieOptions);
|
||||
|
||||
var redirectContext = new TwitterRedirectToAuthorizationEndpointContext(
|
||||
Context, Options,
|
||||
properties, twitterAuthenticationEndpoint);
|
||||
await Options.Events.RedirectToAuthorizationEndpoint(redirectContext);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogError("requestToken CallbackConfirmed!=true");
|
||||
}
|
||||
return false; // REVIEW: Make sure this should not stop other handlers
|
||||
}
|
||||
|
||||
public async Task<bool> InvokeReturnPathAsync()
|
||||
{
|
||||
var model = await HandleAuthenticateOnceAsync();
|
||||
if (model == null)
|
||||
{
|
||||
Logger.LogWarning("Invalid return state, unable to redirect.");
|
||||
Response.StatusCode = 500;
|
||||
return true;
|
||||
}
|
||||
|
||||
var context = new SigningInContext(Context, model)
|
||||
{
|
||||
SignInScheme = Options.SignInScheme,
|
||||
RedirectUri = model.Properties.RedirectUri
|
||||
HttpOnly = true,
|
||||
Secure = Request.IsHttps
|
||||
};
|
||||
model.Properties.RedirectUri = null;
|
||||
|
||||
await Options.Events.SigningIn(context);
|
||||
Response.Cookies.Append(StateCookie, Options.StateDataFormat.Protect(requestToken), cookieOptions);
|
||||
|
||||
if (context.SignInScheme != null && context.Principal != null)
|
||||
{
|
||||
await Context.Authentication.SignInAsync(context.SignInScheme, context.Principal, context.Properties);
|
||||
}
|
||||
|
||||
if (!context.IsRequestCompleted && context.RedirectUri != null)
|
||||
{
|
||||
if (context.Principal == null)
|
||||
{
|
||||
// add a redirect hint that sign-in failed in some way
|
||||
context.RedirectUri = QueryHelpers.AddQueryString(context.RedirectUri, "error", "access_denied");
|
||||
}
|
||||
Response.Redirect(context.RedirectUri);
|
||||
context.RequestCompleted();
|
||||
}
|
||||
|
||||
return context.IsRequestCompleted;
|
||||
}
|
||||
|
||||
protected override Task HandleSignOutAsync(SignOutContext context)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
protected override Task HandleSignInAsync(SignInContext context)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
protected override Task<bool> HandleForbiddenAsync(ChallengeContext context)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
var redirectContext = new TwitterRedirectToAuthorizationEndpointContext(
|
||||
Context, Options,
|
||||
properties, twitterAuthenticationEndpoint);
|
||||
await Options.Events.RedirectToAuthorizationEndpoint(redirectContext);
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<RequestToken> ObtainRequestTokenAsync(string consumerKey, string consumerSecret, string callBackUri, AuthenticationProperties properties)
|
||||
|
|
@ -275,12 +196,12 @@ namespace Microsoft.AspNet.Authentication.Twitter
|
|||
string responseText = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var responseParameters = new FormCollection(FormReader.ReadForm(responseText));
|
||||
if (string.Equals(responseParameters["oauth_callback_confirmed"], "true", StringComparison.Ordinal))
|
||||
if (!string.Equals(responseParameters["oauth_callback_confirmed"], "true", StringComparison.Ordinal))
|
||||
{
|
||||
return new RequestToken { Token = Uri.UnescapeDataString(responseParameters["oauth_token"]), TokenSecret = Uri.UnescapeDataString(responseParameters["oauth_token_secret"]), CallbackConfirmed = true, Properties = properties };
|
||||
throw new Exception("Twitter oauth_callback_confirmed is not true.");
|
||||
}
|
||||
|
||||
return new RequestToken();
|
||||
return new RequestToken { Token = Uri.UnescapeDataString(responseParameters["oauth_token"]), TokenSecret = Uri.UnescapeDataString(responseParameters["oauth_token_secret"]), CallbackConfirmed = true, Properties = properties };
|
||||
}
|
||||
|
||||
private async Task<AccessToken> ObtainAccessTokenAsync(string consumerKey, string consumerSecret, RequestToken token, string verifier)
|
||||
|
|
|
|||
|
|
@ -78,6 +78,10 @@ namespace Microsoft.AspNet.Authentication.Twitter
|
|||
{
|
||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.ConsumerKey)));
|
||||
}
|
||||
if (!Options.CallbackPath.HasValue)
|
||||
{
|
||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.CallbackPath)));
|
||||
}
|
||||
|
||||
if (Options.Events == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ namespace Microsoft.AspNet.Authentication.Twitter
|
|||
/// <summary>
|
||||
/// Options for the Twitter authentication middleware.
|
||||
/// </summary>
|
||||
public class TwitterOptions : AuthenticationOptions
|
||||
public class TwitterOptions : RemoteAuthenticationOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TwitterOptions"/> class.
|
||||
|
|
@ -21,6 +21,7 @@ namespace Microsoft.AspNet.Authentication.Twitter
|
|||
DisplayName = AuthenticationScheme;
|
||||
CallbackPath = new PathString("/signin-twitter");
|
||||
BackchannelTimeout = TimeSpan.FromSeconds(60);
|
||||
Events = new TwitterEvents();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -35,45 +36,6 @@ namespace Microsoft.AspNet.Authentication.Twitter
|
|||
/// <value>The consumer secret used to sign requests to Twitter.</value>
|
||||
public string ConsumerSecret { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets timeout value in milliseconds for back channel communications with Twitter.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The back channel timeout.
|
||||
/// </value>
|
||||
public TimeSpan BackchannelTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The HttpMessageHandler used to communicate with Twitter.
|
||||
/// This cannot be set at the same time as BackchannelCertificateValidator unless the value
|
||||
/// can be downcast to a WebRequestHandler.
|
||||
/// </summary>
|
||||
public HttpMessageHandler BackchannelHttpHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get or sets the text that the user can display on a sign in user interface.
|
||||
/// </summary>
|
||||
public string DisplayName
|
||||
{
|
||||
get { return Description.DisplayName; }
|
||||
set { Description.DisplayName = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The request path within the application's base path where the user-agent will be returned.
|
||||
/// The middleware will process this request when it arrives.
|
||||
/// Default value is "/signin-twitter".
|
||||
/// </summary>
|
||||
public PathString CallbackPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication scheme corresponding to the middleware
|
||||
/// responsible of persisting user's identity after a successful authentication.
|
||||
/// This value typically corresponds to a cookie middleware registered in the Startup class.
|
||||
/// When omitted, <see cref="SharedAuthenticationOptions.SignInScheme"/> is used as a fallback value.
|
||||
/// </summary>
|
||||
public string SignInScheme { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type used to secure data handled by the middleware.
|
||||
/// </summary>
|
||||
|
|
@ -82,7 +44,11 @@ namespace Microsoft.AspNet.Authentication.Twitter
|
|||
/// <summary>
|
||||
/// Gets or sets the <see cref="ITwitterEvents"/> used to handle authentication events.
|
||||
/// </summary>
|
||||
public ITwitterEvents Events { get; set; }
|
||||
public new ITwitterEvents Events
|
||||
{
|
||||
get { return (ITwitterEvents)base.Events; }
|
||||
set { base.Events = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines whether access tokens should be stored in the
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNet.Http.Authentication;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the result of an Authenticate call
|
||||
/// </summary>
|
||||
public class AuthenticateResult
|
||||
{
|
||||
private AuthenticateResult() { }
|
||||
|
||||
/// <summary>
|
||||
/// If a ticket was produced, authenticate was successful.
|
||||
/// </summary>
|
||||
public bool Succeeded
|
||||
{
|
||||
get
|
||||
{
|
||||
return Ticket != null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The authentication ticket.
|
||||
/// </summary>
|
||||
public AuthenticationTicket Ticket { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds error information caused by authentication.
|
||||
/// </summary>
|
||||
public Exception Error { get; private set; }
|
||||
|
||||
public static AuthenticateResult Success(AuthenticationTicket ticket)
|
||||
{
|
||||
if (ticket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(ticket));
|
||||
}
|
||||
return new AuthenticateResult() { Ticket = ticket };
|
||||
}
|
||||
|
||||
public static AuthenticateResult Failed(Exception error)
|
||||
{
|
||||
return new AuthenticateResult() { Error = error };
|
||||
}
|
||||
|
||||
public static AuthenticateResult Failed(string errorMessage)
|
||||
{
|
||||
return new AuthenticateResult() { Error = new Exception(errorMessage) };
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Authentication;
|
||||
using Microsoft.AspNet.Http.Features.Authentication;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -17,7 +18,7 @@ namespace Microsoft.AspNet.Authentication
|
|||
/// <typeparam name="TOptions">Specifies which type for of AuthenticationOptions property</typeparam>
|
||||
public abstract class AuthenticationHandler<TOptions> : IAuthenticationHandler where TOptions : AuthenticationOptions
|
||||
{
|
||||
private Task<AuthenticationTicket> _authenticateTask;
|
||||
private Task<AuthenticateResult> _authenticateTask;
|
||||
private bool _finishCalled;
|
||||
|
||||
protected bool SignInAccepted { get; set; }
|
||||
|
|
@ -96,10 +97,10 @@ namespace Microsoft.AspNet.Authentication
|
|||
|
||||
Response.OnStarting(OnStartingCallback, this);
|
||||
|
||||
// Automatic authentication is the empty scheme
|
||||
if (ShouldHandleScheme(string.Empty))
|
||||
if (ShouldHandleScheme(AuthenticationManager.AutomaticScheme, Options.AutomaticAuthenticate))
|
||||
{
|
||||
var ticket = await HandleAuthenticateOnceAsync();
|
||||
var result = await HandleAuthenticateOnceAsync();
|
||||
var ticket = result?.Ticket;
|
||||
if (ticket?.Principal != null)
|
||||
{
|
||||
Context.User = SecurityHelper.MergeUserPrincipal(Context.User, ticket.Principal);
|
||||
|
|
@ -139,7 +140,7 @@ namespace Microsoft.AspNet.Authentication
|
|||
|
||||
private async Task HandleAutomaticChallengeIfNeeded()
|
||||
{
|
||||
if (!ChallengeCalled && Options.AutomaticAuthentication && Response.StatusCode == 401)
|
||||
if (!ChallengeCalled && Options.AutomaticChallenge && Response.StatusCode == 401)
|
||||
{
|
||||
await HandleUnauthorizedAsync(new ChallengeContext(Options.AuthenticationScheme));
|
||||
}
|
||||
|
|
@ -169,7 +170,7 @@ namespace Microsoft.AspNet.Authentication
|
|||
/// <returns>Returning false will cause the common code to call the next middleware in line. Returning true will
|
||||
/// cause the common code to begin the async completion journey without calling the rest of the middleware
|
||||
/// pipeline.</returns>
|
||||
public virtual Task<bool> InvokeAsync()
|
||||
public virtual Task<bool> HandleRequestAsync()
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
|
@ -184,35 +185,46 @@ namespace Microsoft.AspNet.Authentication
|
|||
}
|
||||
}
|
||||
|
||||
public bool ShouldHandleScheme(string authenticationScheme)
|
||||
public bool ShouldHandleScheme(string authenticationScheme, bool handleAutomatic)
|
||||
{
|
||||
return string.Equals(Options.AuthenticationScheme, authenticationScheme, StringComparison.Ordinal) ||
|
||||
(Options.AutomaticAuthentication && string.IsNullOrEmpty(authenticationScheme));
|
||||
(handleAutomatic && string.Equals(authenticationScheme, AuthenticationManager.AutomaticScheme, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
public async Task AuthenticateAsync(AuthenticateContext context)
|
||||
{
|
||||
if (ShouldHandleScheme(context.AuthenticationScheme))
|
||||
var handled = false;
|
||||
if (ShouldHandleScheme(context.AuthenticationScheme, Options.AutomaticAuthenticate))
|
||||
{
|
||||
// Calling Authenticate more than once should always return the original value.
|
||||
var ticket = await HandleAuthenticateOnceAsync();
|
||||
if (ticket?.Principal != null)
|
||||
var result = await HandleAuthenticateOnceAsync();
|
||||
|
||||
if (result?.Error != null)
|
||||
{
|
||||
context.Authenticated(ticket.Principal, ticket.Properties.Items, Options.Description.Items);
|
||||
context.Failed(result.Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.NotAuthenticated();
|
||||
var ticket = result?.Ticket;
|
||||
if (ticket?.Principal != null)
|
||||
{
|
||||
context.Authenticated(ticket.Principal, ticket.Properties.Items, Options.Description.Items);
|
||||
handled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
context.NotAuthenticated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (PriorHandler != null)
|
||||
if (PriorHandler != null && !handled)
|
||||
{
|
||||
await PriorHandler.AuthenticateAsync(context);
|
||||
}
|
||||
}
|
||||
|
||||
protected Task<AuthenticationTicket> HandleAuthenticateOnceAsync()
|
||||
protected Task<AuthenticateResult> HandleAuthenticateOnceAsync()
|
||||
{
|
||||
if (_authenticateTask == null)
|
||||
{
|
||||
|
|
@ -221,18 +233,17 @@ namespace Microsoft.AspNet.Authentication
|
|||
return _authenticateTask;
|
||||
}
|
||||
|
||||
protected abstract Task<AuthenticationTicket> HandleAuthenticateAsync();
|
||||
protected abstract Task<AuthenticateResult> HandleAuthenticateAsync();
|
||||
|
||||
public async Task SignInAsync(SignInContext context)
|
||||
{
|
||||
if (ShouldHandleScheme(context.AuthenticationScheme))
|
||||
if (ShouldHandleScheme(context.AuthenticationScheme, handleAutomatic: false))
|
||||
{
|
||||
SignInAccepted = true;
|
||||
await HandleSignInAsync(context);
|
||||
context.Accept();
|
||||
}
|
||||
|
||||
if (PriorHandler != null)
|
||||
else if (PriorHandler != null)
|
||||
{
|
||||
await PriorHandler.SignInAsync(context);
|
||||
}
|
||||
|
|
@ -245,14 +256,13 @@ namespace Microsoft.AspNet.Authentication
|
|||
|
||||
public async Task SignOutAsync(SignOutContext context)
|
||||
{
|
||||
if (ShouldHandleScheme(context.AuthenticationScheme))
|
||||
if (ShouldHandleScheme(context.AuthenticationScheme, handleAutomatic: false))
|
||||
{
|
||||
SignOutAccepted = true;
|
||||
await HandleSignOutAsync(context);
|
||||
context.Accept();
|
||||
}
|
||||
|
||||
if (PriorHandler != null)
|
||||
else if (PriorHandler != null)
|
||||
{
|
||||
await PriorHandler.SignOutAsync(context);
|
||||
}
|
||||
|
|
@ -263,10 +273,6 @@ namespace Microsoft.AspNet.Authentication
|
|||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns>True if no other handlers should be called</returns>
|
||||
protected virtual Task<bool> HandleForbiddenAsync(ChallengeContext context)
|
||||
{
|
||||
Response.StatusCode = 403;
|
||||
|
|
@ -288,24 +294,20 @@ namespace Microsoft.AspNet.Authentication
|
|||
|
||||
public async Task ChallengeAsync(ChallengeContext context)
|
||||
{
|
||||
bool handled = false;
|
||||
ChallengeCalled = true;
|
||||
if (ShouldHandleScheme(context.AuthenticationScheme))
|
||||
var handled = false;
|
||||
if (ShouldHandleScheme(context.AuthenticationScheme, Options.AutomaticChallenge))
|
||||
{
|
||||
switch (context.Behavior)
|
||||
{
|
||||
case ChallengeBehavior.Automatic:
|
||||
// If there is a principal already, invoke the forbidden code path
|
||||
var ticket = await HandleAuthenticateOnceAsync();
|
||||
if (ticket?.Principal != null)
|
||||
var result = await HandleAuthenticateOnceAsync();
|
||||
if (result?.Ticket?.Principal != null)
|
||||
{
|
||||
handled = await HandleForbiddenAsync(context);
|
||||
goto case ChallengeBehavior.Forbidden;
|
||||
}
|
||||
else
|
||||
{
|
||||
handled = await HandleUnauthorizedAsync(context);
|
||||
}
|
||||
break;
|
||||
goto case ChallengeBehavior.Unauthorized;
|
||||
case ChallengeBehavior.Unauthorized:
|
||||
handled = await HandleUnauthorizedAsync(context);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ namespace Microsoft.AspNet.Authentication
|
|||
await handler.InitializeAsync(Options, context, Logger, UrlEncoder);
|
||||
try
|
||||
{
|
||||
if (!await handler.InvokeAsync())
|
||||
if (!await handler.HandleRequestAsync())
|
||||
{
|
||||
await _next(context);
|
||||
}
|
||||
|
|
@ -84,7 +84,6 @@ namespace Microsoft.AspNet.Authentication
|
|||
}
|
||||
throw;
|
||||
}
|
||||
await handler.TeardownAsync();
|
||||
}
|
||||
|
||||
protected abstract AuthenticationHandler<TOptions> CreateHandler();
|
||||
|
|
|
|||
|
|
@ -27,11 +27,16 @@ namespace Microsoft.AspNet.Authentication
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// If true the authentication middleware alter the request user coming in and
|
||||
/// alter 401 Unauthorized responses going out. If false the authentication middleware will only provide
|
||||
/// identity and alter responses when explicitly indicated by the AuthenticationScheme.
|
||||
/// If true the authentication middleware alter the request user coming in. If false the authentication middleware will only provide
|
||||
/// identity when explicitly indicated by the AuthenticationScheme.
|
||||
/// </summary>
|
||||
public bool AutomaticAuthentication { get; set; }
|
||||
public bool AutomaticAuthenticate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true the authentication middleware should handle automatic challenge.
|
||||
/// If false the authentication middleware will only alter responses when explicitly indicated by the AuthenticationScheme.
|
||||
/// </summary>
|
||||
public bool AutomaticChallenge { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the issuer that should be used for any claims that are created
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class used for certain event contexts
|
||||
/// </summary>
|
||||
public abstract class BaseContext<TOptions>
|
||||
{
|
||||
protected BaseContext(HttpContext context, TOptions options)
|
||||
{
|
||||
HttpContext = context;
|
||||
Options = options;
|
||||
}
|
||||
|
||||
public HttpContext HttpContext { get; private set; }
|
||||
|
||||
public TOptions Options { get; private set; }
|
||||
|
||||
public HttpRequest Request
|
||||
{
|
||||
get { return HttpContext.Request; }
|
||||
}
|
||||
|
||||
public HttpResponse Response
|
||||
{
|
||||
get { return HttpContext.Response; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,9 +5,9 @@ using Microsoft.AspNet.Http;
|
|||
|
||||
namespace Microsoft.AspNet.Authentication
|
||||
{
|
||||
public class BaseControlContext<TOptions> : BaseContext<TOptions>
|
||||
public class BaseControlContext : BaseContext
|
||||
{
|
||||
protected BaseControlContext(HttpContext context, TOptions options) : base(context, options)
|
||||
protected BaseControlContext(HttpContext context) : base(context)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides error context information to middleware providers.
|
||||
/// </summary>
|
||||
public class ErrorContext : BaseControlContext
|
||||
{
|
||||
public ErrorContext(HttpContext context, Exception error)
|
||||
: base(context)
|
||||
{
|
||||
Error = error;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// User friendly error message for the error.
|
||||
/// </summary>
|
||||
public Exception Error { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication
|
||||
{
|
||||
public interface IRemoteAuthenticationEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoked when the remote authentication process has an error.
|
||||
/// </summary>
|
||||
Task RemoteError(ErrorContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Invoked before sign in.
|
||||
/// </summary>
|
||||
Task TicketReceived(TicketReceivedContext context);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication
|
||||
{
|
||||
public class RemoteAuthenticationEvents : IRemoteAuthenticationEvents
|
||||
{
|
||||
public Func<ErrorContext, Task> OnRemoteError { get; set; } = context => Task.FromResult(0);
|
||||
|
||||
public Func<TicketReceivedContext, Task> OnTicketReceived { get; set; } = context => Task.FromResult(0);
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when there is a remote error
|
||||
/// </summary>
|
||||
public virtual Task RemoteError(ErrorContext context) => OnRemoteError(context);
|
||||
|
||||
/// <summary>
|
||||
/// Invoked after the remote ticket has been recieved.
|
||||
/// </summary>
|
||||
public virtual Task TicketReceived(TicketReceivedContext context) => OnTicketReceived(context);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Authentication;
|
||||
|
|
@ -11,13 +10,12 @@ namespace Microsoft.AspNet.Authentication
|
|||
/// <summary>
|
||||
/// Provides context information to middleware providers.
|
||||
/// </summary>
|
||||
public class SigningInContext : BaseContext
|
||||
public class TicketReceivedContext : BaseControlContext
|
||||
{
|
||||
public SigningInContext(
|
||||
HttpContext context,
|
||||
AuthenticationTicket ticket)
|
||||
public TicketReceivedContext(HttpContext context, RemoteAuthenticationOptions options, AuthenticationTicket ticket)
|
||||
: base(context)
|
||||
{
|
||||
Options = options;
|
||||
if (ticket != null)
|
||||
{
|
||||
Principal = ticket.Principal;
|
||||
|
|
@ -27,17 +25,8 @@ namespace Microsoft.AspNet.Authentication
|
|||
|
||||
public ClaimsPrincipal Principal { get; set; }
|
||||
public AuthenticationProperties Properties { get; set; }
|
||||
public RemoteAuthenticationOptions Options { get; set; }
|
||||
|
||||
public bool IsRequestCompleted { get; private set; }
|
||||
|
||||
public void RequestCompleted()
|
||||
{
|
||||
IsRequestCompleted = true;
|
||||
}
|
||||
|
||||
public string SignInScheme { get; set; }
|
||||
|
||||
[SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "By design")]
|
||||
public string RedirectUri { get; set; }
|
||||
public string ReturnUri { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http.Features.Authentication;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication
|
||||
{
|
||||
public abstract class RemoteAuthenticationHandler<TOptions> : AuthenticationHandler<TOptions> where TOptions : RemoteAuthenticationOptions
|
||||
{
|
||||
public override async Task<bool> HandleRequestAsync()
|
||||
{
|
||||
if (Options.CallbackPath == Request.Path)
|
||||
{
|
||||
return await HandleRemoteCallbackAsync();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual async Task<bool> HandleRemoteCallbackAsync()
|
||||
{
|
||||
var authResult = await HandleRemoteAuthenticateAsync();
|
||||
if (authResult == null || !authResult.Succeeded)
|
||||
{
|
||||
var errorContext = new ErrorContext(Context, authResult?.Error ?? new Exception("Invalid return state, unable to redirect."));
|
||||
Logger.LogInformation("Error from RemoteAuthentication: " + errorContext.Error.Message);
|
||||
await Options.Events.RemoteError(errorContext);
|
||||
if (errorContext.HandledResponse)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (errorContext.Skipped)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Context.Response.StatusCode = 500;
|
||||
return true;
|
||||
}
|
||||
|
||||
// We have a ticket if we get here
|
||||
var ticket = authResult.Ticket;
|
||||
var context = new TicketReceivedContext(Context, Options, ticket)
|
||||
{
|
||||
ReturnUri = ticket.Properties.RedirectUri,
|
||||
};
|
||||
// REVIEW: is this safe or good?
|
||||
ticket.Properties.RedirectUri = null;
|
||||
|
||||
await Options.Events.TicketReceived(context);
|
||||
|
||||
if (context.HandledResponse)
|
||||
{
|
||||
Logger.LogVerbose("The SigningIn event returned Handled.");
|
||||
return true;
|
||||
}
|
||||
else if (context.Skipped)
|
||||
{
|
||||
Logger.LogVerbose("The SigningIn event returned Skipped.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (context.Principal != null)
|
||||
{
|
||||
await Context.Authentication.SignInAsync(Options.SignInScheme, context.Principal, context.Properties);
|
||||
}
|
||||
|
||||
if (context.ReturnUri != null)
|
||||
{
|
||||
Response.Redirect(context.ReturnUri);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract Task<AuthenticateResult> HandleRemoteAuthenticateAsync();
|
||||
|
||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
return Task.FromResult(AuthenticateResult.Failed("Remote authentication does not support authenticate"));
|
||||
}
|
||||
|
||||
protected override Task HandleSignOutAsync(SignOutContext context)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
protected override Task HandleSignInAsync(SignInContext context)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
protected override Task<bool> HandleForbiddenAsync(ChallengeContext context)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication
|
||||
{
|
||||
public class RemoteAuthenticationOptions : AuthenticationOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets timeout value in milliseconds for back channel communications with Twitter.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The back channel timeout.
|
||||
/// </value>
|
||||
public TimeSpan BackchannelTimeout { get; set; } = TimeSpan.FromSeconds(60);
|
||||
|
||||
/// <summary>
|
||||
/// The HttpMessageHandler used to communicate with Twitter.
|
||||
/// This cannot be set at the same time as BackchannelCertificateValidator unless the value
|
||||
/// can be downcast to a WebRequestHandler.
|
||||
/// </summary>
|
||||
public HttpMessageHandler BackchannelHttpHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The request path within the application's base path where the user-agent will be returned.
|
||||
/// The middleware will process this request when it arrives.
|
||||
/// </summary>
|
||||
public PathString CallbackPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication scheme corresponding to the middleware
|
||||
/// responsible of persisting user's identity after a successful authentication.
|
||||
/// This value typically corresponds to a cookie middleware registered in the Startup class.
|
||||
/// When omitted, <see cref="SharedAuthenticationOptions.SignInScheme"/> is used as a fallback value.
|
||||
/// </summary>
|
||||
public string SignInScheme { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get or sets the text that the user can display on a sign in user interface.
|
||||
/// </summary>
|
||||
public string DisplayName
|
||||
{
|
||||
get { return Description.DisplayName; }
|
||||
set { Description.DisplayName = value; }
|
||||
}
|
||||
|
||||
public IRemoteAuthenticationEvents Events = new RemoteAuthenticationEvents();
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,15 @@
|
|||
"Microsoft.Extensions.WebEncoders": "1.0.0-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"dnx451": { },
|
||||
"dnxcore50": { }
|
||||
"dnx451": {
|
||||
"frameworkAssemblies": {
|
||||
"System.Net.Http": ""
|
||||
}
|
||||
},
|
||||
"dnxcore50": {
|
||||
"dependencies": {
|
||||
"System.Net.Http": "4.0.1-beta-*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,16 +9,16 @@ namespace Microsoft.AspNet.Authorization
|
|||
{
|
||||
public class AuthorizationPolicy
|
||||
{
|
||||
public AuthorizationPolicy(IEnumerable<IAuthorizationRequirement> requirements, IEnumerable<string> activeAuthenticationSchemes)
|
||||
public AuthorizationPolicy(IEnumerable<IAuthorizationRequirement> requirements, IEnumerable<string> authenticationSchemes)
|
||||
{
|
||||
if (requirements == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(requirements));
|
||||
}
|
||||
|
||||
if (activeAuthenticationSchemes == null)
|
||||
if (authenticationSchemes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(activeAuthenticationSchemes));
|
||||
throw new ArgumentNullException(nameof(authenticationSchemes));
|
||||
}
|
||||
|
||||
if (requirements.Count() == 0)
|
||||
|
|
@ -26,11 +26,11 @@ namespace Microsoft.AspNet.Authorization
|
|||
throw new InvalidOperationException(Resources.Exception_AuthorizationPolicyEmpty);
|
||||
}
|
||||
Requirements = new List<IAuthorizationRequirement>(requirements).AsReadOnly();
|
||||
ActiveAuthenticationSchemes = new List<string>(activeAuthenticationSchemes).AsReadOnly();
|
||||
AuthenticationSchemes = new List<string>(authenticationSchemes).AsReadOnly();
|
||||
}
|
||||
|
||||
public IReadOnlyList<IAuthorizationRequirement> Requirements { get; }
|
||||
public IReadOnlyList<string> ActiveAuthenticationSchemes { get; }
|
||||
public IReadOnlyList<string> AuthenticationSchemes { get; }
|
||||
|
||||
public static AuthorizationPolicy Combine(params AuthorizationPolicy[] policies)
|
||||
{
|
||||
|
|
@ -96,7 +96,7 @@ namespace Microsoft.AspNet.Authorization
|
|||
{
|
||||
foreach (var authType in authTypesSplit)
|
||||
{
|
||||
policyBuilder.ActiveAuthenticationSchemes.Add(authType);
|
||||
policyBuilder.AuthenticationSchemes.Add(authType);
|
||||
}
|
||||
}
|
||||
if (useDefaultPolicy)
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ namespace Microsoft.AspNet.Authorization
|
|||
{
|
||||
public class AuthorizationPolicyBuilder
|
||||
{
|
||||
public AuthorizationPolicyBuilder(params string[] activeAuthenticationSchemes)
|
||||
public AuthorizationPolicyBuilder(params string[] authenticationSchemes)
|
||||
{
|
||||
AddAuthenticationSchemes(activeAuthenticationSchemes);
|
||||
AddAuthenticationSchemes(authenticationSchemes);
|
||||
}
|
||||
|
||||
public AuthorizationPolicyBuilder(AuthorizationPolicy policy)
|
||||
|
|
@ -20,13 +20,13 @@ namespace Microsoft.AspNet.Authorization
|
|||
}
|
||||
|
||||
public IList<IAuthorizationRequirement> Requirements { get; set; } = new List<IAuthorizationRequirement>();
|
||||
public IList<string> ActiveAuthenticationSchemes { get; set; } = new List<string>();
|
||||
public IList<string> AuthenticationSchemes { get; set; } = new List<string>();
|
||||
|
||||
public AuthorizationPolicyBuilder AddAuthenticationSchemes(params string[] activeAuthTypes)
|
||||
public AuthorizationPolicyBuilder AddAuthenticationSchemes(params string[] schemes)
|
||||
{
|
||||
foreach (var authType in activeAuthTypes)
|
||||
foreach (var authType in schemes)
|
||||
{
|
||||
ActiveAuthenticationSchemes.Add(authType);
|
||||
AuthenticationSchemes.Add(authType);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
|
@ -47,7 +47,7 @@ namespace Microsoft.AspNet.Authorization
|
|||
throw new ArgumentNullException(nameof(policy));
|
||||
}
|
||||
|
||||
AddAuthenticationSchemes(policy.ActiveAuthenticationSchemes.ToArray());
|
||||
AddAuthenticationSchemes(policy.AuthenticationSchemes.ToArray());
|
||||
AddRequirements(policy.Requirements.ToArray());
|
||||
return this;
|
||||
}
|
||||
|
|
@ -135,7 +135,7 @@ namespace Microsoft.AspNet.Authorization
|
|||
|
||||
public AuthorizationPolicy Build()
|
||||
{
|
||||
return new AuthorizationPolicy(Requirements, ActiveAuthenticationSchemes.Distinct());
|
||||
return new AuthorizationPolicy(Requirements, AuthenticationSchemes.Distinct());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Authentication;
|
||||
using Microsoft.AspNet.Http.Features;
|
||||
using Microsoft.AspNet.Http.Features.Authentication;
|
||||
using Microsoft.AspNet.Http.Internal;
|
||||
|
|
@ -19,10 +20,10 @@ namespace Microsoft.AspNet.Authentication
|
|||
public async Task ShouldHandleSchemeAreDeterminedOnlyByMatchingAuthenticationScheme()
|
||||
{
|
||||
var handler = await TestHandler.Create("Alpha");
|
||||
var passiveNoMatch = handler.ShouldHandleScheme("Beta");
|
||||
var passiveNoMatch = handler.ShouldHandleScheme("Beta", handleAutomatic: false);
|
||||
|
||||
handler = await TestHandler.Create("Alpha");
|
||||
var passiveWithMatch = handler.ShouldHandleScheme("Alpha");
|
||||
var passiveWithMatch = handler.ShouldHandleScheme("Alpha", handleAutomatic: false);
|
||||
|
||||
Assert.False(passiveNoMatch);
|
||||
Assert.True(passiveWithMatch);
|
||||
|
|
@ -32,47 +33,37 @@ namespace Microsoft.AspNet.Authentication
|
|||
public async Task AutomaticHandlerInAutomaticModeHandlesEmptyChallenges()
|
||||
{
|
||||
var handler = await TestAutoHandler.Create("ignored", true);
|
||||
Assert.True(handler.ShouldHandleScheme(""));
|
||||
Assert.True(handler.ShouldHandleScheme(AuthenticationManager.AutomaticScheme, handleAutomatic: true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AutomaticHandlerHandlesNullScheme()
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
[InlineData("notmatched")]
|
||||
public async Task AutomaticHandlerDoesNotHandleSchemes(string scheme)
|
||||
{
|
||||
var handler = await TestAutoHandler.Create("ignored", true);
|
||||
Assert.True(handler.ShouldHandleScheme(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AutomaticHandlerIgnoresWhitespaceScheme()
|
||||
{
|
||||
var handler = await TestAutoHandler.Create("ignored", true);
|
||||
Assert.False(handler.ShouldHandleScheme(" "));
|
||||
Assert.False(handler.ShouldHandleScheme(scheme, handleAutomatic: true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AutomaticHandlerShouldHandleSchemeWhenSchemeMatches()
|
||||
{
|
||||
var handler = await TestAutoHandler.Create("Alpha", true);
|
||||
Assert.True(handler.ShouldHandleScheme("Alpha"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AutomaticHandlerShouldNotHandleChallengeWhenSchemeDoesNotMatches()
|
||||
{
|
||||
var handler = await TestAutoHandler.Create("Dog", true);
|
||||
Assert.False(handler.ShouldHandleScheme("Alpha"));
|
||||
Assert.True(handler.ShouldHandleScheme("Alpha", handleAutomatic: true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AutomaticHandlerShouldNotHandleChallengeWhenSchemesNotEmpty()
|
||||
{
|
||||
var handler = await TestAutoHandler.Create(null, true);
|
||||
Assert.False(handler.ShouldHandleScheme("Alpha"));
|
||||
Assert.False(handler.ShouldHandleScheme("Alpha", handleAutomatic: true));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Alpha")]
|
||||
[InlineData("")]
|
||||
[InlineData("Automatic")]
|
||||
public async Task AuthHandlerAuthenticateCachesTicket(string scheme)
|
||||
{
|
||||
var handler = await CountHandler.Create(scheme);
|
||||
|
|
@ -100,14 +91,14 @@ namespace Microsoft.AspNet.Authentication
|
|||
new LoggerFactory().CreateLogger("CountHandler"),
|
||||
Extensions.WebEncoders.UrlEncoder.Default);
|
||||
handler.Options.AuthenticationScheme = scheme;
|
||||
handler.Options.AutomaticAuthentication = true;
|
||||
handler.Options.AutomaticAuthenticate = true;
|
||||
return handler;
|
||||
}
|
||||
|
||||
protected override Task<AuthenticationTicket> HandleAuthenticateAsync()
|
||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
AuthCount++;
|
||||
return Task.FromResult(new AuthenticationTicket(null, null));
|
||||
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(new Http.Authentication.AuthenticationProperties(), "whatever")));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -129,9 +120,9 @@ namespace Microsoft.AspNet.Authentication
|
|||
return handler;
|
||||
}
|
||||
|
||||
protected override Task<AuthenticationTicket> HandleAuthenticateAsync()
|
||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
return Task.FromResult<AuthenticationTicket>(null);
|
||||
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(new Http.Authentication.AuthenticationProperties(), "whatever")));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +132,7 @@ namespace Microsoft.AspNet.Authentication
|
|||
{
|
||||
public TestAutoOptions()
|
||||
{
|
||||
AutomaticAuthentication = true;
|
||||
AutomaticAuthenticate = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -159,13 +150,13 @@ namespace Microsoft.AspNet.Authentication
|
|||
new LoggerFactory().CreateLogger("TestAutoHandler"),
|
||||
Extensions.WebEncoders.UrlEncoder.Default);
|
||||
handler.Options.AuthenticationScheme = scheme;
|
||||
handler.Options.AutomaticAuthentication = auto;
|
||||
handler.Options.AutomaticAuthenticate = auto;
|
||||
return handler;
|
||||
}
|
||||
|
||||
protected override Task<AuthenticationTicket> HandleAuthenticateAsync()
|
||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
return Task.FromResult<AuthenticationTicket>(null);
|
||||
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(new Http.Authentication.AuthenticationProperties(), "whatever")));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
var server = CreateServer(options =>
|
||||
{
|
||||
options.LoginPath = new PathString("/login");
|
||||
options.AutomaticAuthentication = auto;
|
||||
options.AutomaticChallenge = auto;
|
||||
});
|
||||
|
||||
var transaction = await SendAsync(server, "http://example.com/protected");
|
||||
|
|
@ -60,7 +60,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
[Fact]
|
||||
public async Task ProtectedCustomRequestShouldRedirectToCustomRedirectUri()
|
||||
{
|
||||
var server = CreateServer(options => options.AutomaticAuthentication = true);
|
||||
var server = CreateServer(options => options.AutomaticChallenge = true);
|
||||
|
||||
var transaction = await SendAsync(server, "http://example.com/protected/CustomRedirect");
|
||||
|
||||
|
|
@ -573,7 +573,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
var clock = new TestClock();
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.AutomaticAuthentication = automatic;
|
||||
options.AutomaticAuthenticate = automatic;
|
||||
options.SystemClock = clock;
|
||||
},
|
||||
SignInAsAlice);
|
||||
|
|
@ -596,7 +596,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
var clock = new TestClock();
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.AutomaticAuthentication = automatic;
|
||||
options.AutomaticAuthenticate = automatic;
|
||||
options.SystemClock = clock;
|
||||
},
|
||||
SignInAsAlice);
|
||||
|
|
@ -617,7 +617,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
var clock = new TestClock();
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.AutomaticAuthentication = automatic;
|
||||
options.AutomaticAuthenticate = automatic;
|
||||
options.SystemClock = clock;
|
||||
},
|
||||
SignInAsAlice);
|
||||
|
|
@ -1002,10 +1002,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
}
|
||||
});
|
||||
},
|
||||
services =>
|
||||
{
|
||||
services.AddAuthentication();
|
||||
});
|
||||
services => services.AddAuthentication());
|
||||
server.BaseAddress = baseAddress;
|
||||
return server;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ using Microsoft.Extensions.DependencyInjection;
|
|||
using Microsoft.Extensions.WebEncoders;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNet.Authentication.Cookies;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication.Facebook
|
||||
{
|
||||
|
|
@ -45,7 +47,7 @@ namespace Microsoft.AspNet.Authentication.Facebook
|
|||
app.UseCookieAuthentication(options =>
|
||||
{
|
||||
options.AuthenticationScheme = "External";
|
||||
options.AutomaticAuthentication = true;
|
||||
options.AutomaticAuthenticate = true;
|
||||
});
|
||||
},
|
||||
services =>
|
||||
|
|
@ -158,11 +160,12 @@ namespace Microsoft.AspNet.Authentication.Facebook
|
|||
public async Task CustomUserInfoEndpointHasValidGraphQuery()
|
||||
{
|
||||
var customUserInfoEndpoint = "https://graph.facebook.com/me?fields=email,timezone,picture";
|
||||
string finalUserInfoEndpoint = string.Empty;
|
||||
var finalUserInfoEndpoint = string.Empty;
|
||||
var stateFormat = new PropertiesDataFormat(new EphemeralDataProtectionProvider().CreateProtector("FacebookTest"));
|
||||
var server = CreateServer(
|
||||
app =>
|
||||
{
|
||||
app.UseCookieAuthentication();
|
||||
app.UseFacebookAuthentication(options =>
|
||||
{
|
||||
options.AppId = "Test App Id";
|
||||
|
|
@ -200,11 +203,10 @@ namespace Microsoft.AspNet.Authentication.Facebook
|
|||
}
|
||||
};
|
||||
});
|
||||
app.UseCookieAuthentication();
|
||||
},
|
||||
services =>
|
||||
{
|
||||
services.AddAuthentication();
|
||||
services.AddAuthentication(options => options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
}, handler: null);
|
||||
|
||||
var properties = new AuthenticationProperties();
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ using Microsoft.AspNet.Builder;
|
|||
using Microsoft.AspNet.DataProtection;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Authentication;
|
||||
using Microsoft.AspNet.Http.Features.Authentication;
|
||||
using Microsoft.AspNet.TestHost;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.WebEncoders;
|
||||
|
|
@ -88,7 +89,7 @@ namespace Microsoft.AspNet.Authentication.Google
|
|||
{
|
||||
options.ClientId = "Test Id";
|
||||
options.ClientSecret = "Test Secret";
|
||||
options.AutomaticAuthentication = true;
|
||||
options.AutomaticChallenge = true;
|
||||
});
|
||||
var transaction = await server.SendAsync("https://example.com/401");
|
||||
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
||||
|
|
@ -119,7 +120,7 @@ namespace Microsoft.AspNet.Authentication.Google
|
|||
{
|
||||
options.ClientId = "Test Id";
|
||||
options.ClientSecret = "Test Secret";
|
||||
options.AutomaticAuthentication = true;
|
||||
options.AutomaticChallenge = true;
|
||||
});
|
||||
var transaction = await server.SendAsync("https://example.com/401");
|
||||
Assert.Contains(".AspNet.Correlation.Google=", transaction.SetCookie.Single());
|
||||
|
|
@ -146,7 +147,7 @@ namespace Microsoft.AspNet.Authentication.Google
|
|||
{
|
||||
options.ClientId = "Test Id";
|
||||
options.ClientSecret = "Test Secret";
|
||||
options.AutomaticAuthentication = true;
|
||||
options.AutomaticChallenge = true;
|
||||
});
|
||||
var transaction = await server.SendAsync("https://example.com/401");
|
||||
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
||||
|
|
@ -161,7 +162,7 @@ namespace Microsoft.AspNet.Authentication.Google
|
|||
{
|
||||
options.ClientId = "Test Id";
|
||||
options.ClientSecret = "Test Secret";
|
||||
options.AutomaticAuthentication = true;
|
||||
options.AutomaticChallenge = true;
|
||||
},
|
||||
context =>
|
||||
{
|
||||
|
|
@ -212,6 +213,29 @@ namespace Microsoft.AspNet.Authentication.Google
|
|||
Assert.Contains("custom=test", query);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AuthenticateWillFail()
|
||||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.ClientId = "Test Id";
|
||||
options.ClientSecret = "Test Secret";
|
||||
},
|
||||
async context =>
|
||||
{
|
||||
var req = context.Request;
|
||||
var res = context.Response;
|
||||
if (req.Path == new PathString("/auth"))
|
||||
{
|
||||
var auth = new AuthenticateContext("Google");
|
||||
await context.Authentication.AuthenticateAsync(auth);
|
||||
Assert.NotNull(auth.Error);
|
||||
}
|
||||
});
|
||||
var transaction = await server.SendAsync("https://example.com/auth");
|
||||
Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReplyPathWithoutStateQueryStringWillBeRejected()
|
||||
{
|
||||
|
|
@ -224,6 +248,40 @@ namespace Microsoft.AspNet.Authentication.Google
|
|||
Assert.Equal(HttpStatusCode.InternalServerError, transaction.Response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task ReplyPathWithErrorFails(bool redirect)
|
||||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.ClientId = "Test Id";
|
||||
options.ClientSecret = "Test Secret";
|
||||
if (redirect)
|
||||
{
|
||||
options.Events = new OAuthEvents()
|
||||
{
|
||||
OnRemoteError = ctx =>
|
||||
{
|
||||
ctx.Response.Redirect("/error?ErrorMessage=" + ctx.Error.Message);
|
||||
ctx.HandleResponse();
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
var transaction = await server.SendAsync("https://example.com/signin-google?error=OMG");
|
||||
if (redirect)
|
||||
{
|
||||
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
||||
Assert.Equal("/error?ErrorMessage=OMG", transaction.Response.Headers.GetValues("Location").First());
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(HttpStatusCode.InternalServerError, transaction.Response.StatusCode);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("CustomIssuer")]
|
||||
|
|
@ -305,8 +363,11 @@ namespace Microsoft.AspNet.Authentication.Google
|
|||
Assert.Equal("yup", transaction.FindClaimValue("xform"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReplyPathWillRejectIfCodeIsInvalid()
|
||||
// REVIEW: Fix this once we revisit error handling to not blow up
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task ReplyPathWillThrowIfCodeIsInvalid(bool redirect)
|
||||
{
|
||||
var stateFormat = new PropertiesDataFormat(new EphemeralDataProtectionProvider().CreateProtector("GoogleTest"));
|
||||
var server = CreateServer(options =>
|
||||
|
|
@ -321,22 +382,50 @@ namespace Microsoft.AspNet.Authentication.Google
|
|||
return new HttpResponseMessage(HttpStatusCode.BadRequest);
|
||||
}
|
||||
};
|
||||
if (redirect)
|
||||
{
|
||||
options.Events = new OAuthEvents()
|
||||
{
|
||||
OnRemoteError = ctx =>
|
||||
{
|
||||
ctx.Response.Redirect("/error?ErrorMessage=" + ctx.Error.Message);
|
||||
ctx.HandleResponse();
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
var properties = new AuthenticationProperties();
|
||||
var correlationKey = ".AspNet.Correlation.Google";
|
||||
var correlationValue = "TestCorrelationId";
|
||||
properties.Items.Add(correlationKey, correlationValue);
|
||||
properties.RedirectUri = "/me";
|
||||
|
||||
var state = stateFormat.Protect(properties);
|
||||
var transaction = await server.SendAsync(
|
||||
|
||||
await Assert.ThrowsAsync<HttpRequestException>(() => server.SendAsync(
|
||||
"https://example.com/signin-google?code=TestCode&state=" + UrlEncoder.Default.UrlEncode(state),
|
||||
correlationKey + "=" + correlationValue);
|
||||
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
||||
Assert.Contains("error=access_denied", transaction.Response.Headers.Location.ToString());
|
||||
correlationKey + "=" + correlationValue));
|
||||
|
||||
//var transaction = await server.SendAsync(
|
||||
// "https://example.com/signin-google?code=TestCode&state=" + UrlEncoder.Default.UrlEncode(state),
|
||||
// correlationKey + "=" + correlationValue);
|
||||
//if (redirect)
|
||||
//{
|
||||
// Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
||||
// Assert.Equal("/error?ErrorMessage=" + UrlEncoder.Default.UrlEncode("Access token was not found."),
|
||||
// transaction.Response.Headers.GetValues("Location").First());
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// Assert.Equal(HttpStatusCode.InternalServerError, transaction.Response.StatusCode);
|
||||
//}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReplyPathWillRejectIfAccessTokenIsMissing()
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task ReplyPathWillRejectIfAccessTokenIsMissing(bool redirect)
|
||||
{
|
||||
var stateFormat = new PropertiesDataFormat(new EphemeralDataProtectionProvider().CreateProtector("GoogleTest"));
|
||||
var server = CreateServer(options =>
|
||||
|
|
@ -351,6 +440,18 @@ namespace Microsoft.AspNet.Authentication.Google
|
|||
return ReturnJsonResponse(new object());
|
||||
}
|
||||
};
|
||||
if (redirect)
|
||||
{
|
||||
options.Events = new OAuthEvents()
|
||||
{
|
||||
OnRemoteError = ctx =>
|
||||
{
|
||||
ctx.Response.Redirect("/error?ErrorMessage=" + ctx.Error.Message);
|
||||
ctx.HandleResponse();
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
var properties = new AuthenticationProperties();
|
||||
var correlationKey = ".AspNet.Correlation.Google";
|
||||
|
|
@ -361,8 +462,16 @@ namespace Microsoft.AspNet.Authentication.Google
|
|||
var transaction = await server.SendAsync(
|
||||
"https://example.com/signin-google?code=TestCode&state=" + UrlEncoder.Default.UrlEncode(state),
|
||||
correlationKey + "=" + correlationValue);
|
||||
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
||||
Assert.Contains("error=access_denied", transaction.Response.Headers.Location.ToString());
|
||||
if (redirect)
|
||||
{
|
||||
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
||||
Assert.Equal("/error?ErrorMessage=" + UrlEncoder.Default.UrlEncode("Access token was not found."),
|
||||
transaction.Response.Headers.GetValues("Location").First());
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(HttpStatusCode.InternalServerError, transaction.Response.StatusCode);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -420,7 +529,7 @@ namespace Microsoft.AspNet.Authentication.Google
|
|||
{
|
||||
var refreshToken = context.RefreshToken;
|
||||
context.Principal.AddIdentity(new ClaimsIdentity(new Claim[] { new Claim("RefreshToken", refreshToken, ClaimValueTypes.String, "Google") }, "Google"));
|
||||
return Task.FromResult<object>(null);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
@ -529,6 +638,50 @@ namespace Microsoft.AspNet.Authentication.Google
|
|||
Assert.Equal("/foo", transaction.Response.Headers.GetValues("Location").First());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NoStateCauses500()
|
||||
{
|
||||
var stateFormat = new PropertiesDataFormat(new EphemeralDataProtectionProvider().CreateProtector("GoogleTest"));
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.ClientId = "Test Id";
|
||||
options.ClientSecret = "Test Secret";
|
||||
});
|
||||
|
||||
//Post a message to the Google middleware
|
||||
var transaction = await server.SendAsync(
|
||||
"https://example.com/signin-google?code=TestCode");
|
||||
|
||||
Assert.Equal(HttpStatusCode.InternalServerError, transaction.Response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanRedirectOnError()
|
||||
{
|
||||
var stateFormat = new PropertiesDataFormat(new EphemeralDataProtectionProvider().CreateProtector("GoogleTest"));
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.ClientId = "Test Id";
|
||||
options.ClientSecret = "Test Secret";
|
||||
options.Events = new OAuthEvents()
|
||||
{
|
||||
OnRemoteError = ctx =>
|
||||
{
|
||||
ctx.Response.Redirect("/error?ErrorMessage=" + ctx.Error.Message);
|
||||
ctx.HandleResponse();
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
//Post a message to the Google middleware
|
||||
var transaction = await server.SendAsync(
|
||||
"https://example.com/signin-google?code=TestCode");
|
||||
|
||||
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
||||
Assert.Equal("/error?ErrorMessage=" + UrlEncoder.Default.UrlEncode("The oauth state was missing or invalid."),
|
||||
transaction.Response.Headers.GetValues("Location").First());
|
||||
}
|
||||
|
||||
private static HttpResponseMessage ReturnJsonResponse(object content)
|
||||
{
|
||||
|
|
@ -545,7 +698,7 @@ namespace Microsoft.AspNet.Authentication.Google
|
|||
app.UseCookieAuthentication(options =>
|
||||
{
|
||||
options.AuthenticationScheme = TestExtensions.CookieAuthenticationScheme;
|
||||
options.AutomaticAuthentication = true;
|
||||
options.AutomaticAuthenticate = true;
|
||||
});
|
||||
app.UseGoogleAuthentication(configureOptions);
|
||||
app.UseClaimsTransformation(p =>
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
|
|||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.AutomaticAuthentication = true;
|
||||
options.AutomaticAuthenticate = true;
|
||||
|
||||
options.Authority = "https://login.windows.net/tushartest.onmicrosoft.com";
|
||||
options.Audience = "https://TusharTest.onmicrosoft.com/TodoListService-ManualJwt";
|
||||
|
|
@ -44,7 +44,7 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
|
|||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.AutomaticAuthentication = true;
|
||||
options.AutomaticAuthenticate = true;
|
||||
});
|
||||
var transaction = await server.SendAsync("https://example.com/signIn");
|
||||
Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode);
|
||||
|
|
@ -55,7 +55,7 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
|
|||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.AutomaticAuthentication = true;
|
||||
options.AutomaticAuthenticate = true;
|
||||
});
|
||||
var transaction = await server.SendAsync("https://example.com/signOut");
|
||||
Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode);
|
||||
|
|
@ -67,7 +67,7 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
|
|||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.AutomaticAuthentication = true;
|
||||
options.AutomaticAuthenticate = true;
|
||||
|
||||
options.Events = new JwtBearerEvents()
|
||||
{
|
||||
|
|
@ -117,7 +117,7 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
|
|||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.AutomaticAuthentication = true;
|
||||
options.AutomaticAuthenticate = true;
|
||||
|
||||
options.Events = new JwtBearerEvents()
|
||||
{
|
||||
|
|
@ -151,7 +151,7 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
|
|||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.AutomaticAuthentication = true;
|
||||
options.AutomaticAuthenticate = true;
|
||||
|
||||
options.Events = new JwtBearerEvents()
|
||||
{
|
||||
|
|
@ -188,7 +188,7 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
|
|||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.AutomaticAuthentication = true;
|
||||
options.AutomaticAuthenticate = true;
|
||||
|
||||
options.Events = new JwtBearerEvents()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ namespace Microsoft.AspNet.Authentication.Tests.MicrosoftAccount
|
|||
app.UseCookieAuthentication(options =>
|
||||
{
|
||||
options.AuthenticationScheme = TestExtensions.CookieAuthenticationScheme;
|
||||
options.AutomaticAuthentication = true;
|
||||
options.AutomaticAuthenticate = true;
|
||||
});
|
||||
app.UseMicrosoftAccountAuthentication(configureOptions);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ using System.IdentityModel.Tokens.Jwt;
|
|||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Authentication.OpenIdConnect;
|
||||
using Microsoft.AspNet.Http.Authentication;
|
||||
using Microsoft.AspNet.Http.Features.Authentication;
|
||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
|
|
@ -17,16 +15,10 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
|
|||
/// </summary>
|
||||
public class OpenIdConnectHandlerForTestingAuthenticate : OpenIdConnectHandler
|
||||
{
|
||||
public OpenIdConnectHandlerForTestingAuthenticate()
|
||||
: base(null)
|
||||
public OpenIdConnectHandlerForTestingAuthenticate() : base(null)
|
||||
{
|
||||
}
|
||||
|
||||
protected override async Task<bool> HandleUnauthorizedAsync(ChallengeContext context)
|
||||
{
|
||||
return await base.HandleUnauthorizedAsync(context);
|
||||
}
|
||||
|
||||
protected override Task<OpenIdConnectTokenEndpointResponse> RedeemAuthorizationCodeAsync(string authorizationCode, string redirectUri)
|
||||
{
|
||||
var jsonResponse = new JObject();
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
|
|||
options.ConfigurationManager = TestUtilities.DefaultOpenIdConnectConfigurationManager;
|
||||
options.ClientId = Guid.NewGuid().ToString();
|
||||
options.StateDataFormat = new AuthenticationPropertiesFormaterKeyValue();
|
||||
options.SignInScheme = "Cookies";
|
||||
options.Events = new OpenIdConnectEvents()
|
||||
{
|
||||
OnTokenResponseReceived = context =>
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
|
|||
var mockOpenIdConnectMessage = new Mock<OpenIdConnectMessage>();
|
||||
mockOpenIdConnectMessage.Setup(m => m.CreateAuthenticationRequestUrl()).Returns(ExpectedAuthorizeRequest);
|
||||
mockOpenIdConnectMessage.Setup(m => m.CreateLogoutRequestUrl()).Returns(ExpectedLogoutRequest);
|
||||
options.AutomaticAuthentication = true;
|
||||
options.AutomaticChallenge = true;
|
||||
options.Events = new OpenIdConnectEvents()
|
||||
{
|
||||
OnRedirectToAuthenticationEndpoint = (context) =>
|
||||
|
|
@ -191,7 +191,7 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
|
|||
var server = CreateServer(options =>
|
||||
{
|
||||
SetOptions(options, DefaultParameters(new string[] { OpenIdConnectParameterNames.State }), queryValues, stateDataFormat);
|
||||
options.AutomaticAuthentication = challenge.Equals(ChallengeWithOutContext);
|
||||
options.AutomaticChallenge = challenge.Equals(ChallengeWithOutContext);
|
||||
options.Events = new OpenIdConnectEvents()
|
||||
{
|
||||
OnRedirectToAuthenticationEndpoint = context =>
|
||||
|
|
@ -306,7 +306,7 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
|
|||
private static void DefaultChallengeOptions(OpenIdConnectOptions options)
|
||||
{
|
||||
options.AuthenticationScheme = "OpenIdConnectHandlerTest";
|
||||
options.AutomaticAuthentication = true;
|
||||
options.AutomaticChallenge = true;
|
||||
options.ClientId = Guid.NewGuid().ToString();
|
||||
options.ConfigurationManager = TestUtilities.DefaultOpenIdConnectConfigurationManager;
|
||||
options.StateDataFormat = new AuthenticationPropertiesFormaterKeyValue();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Security.Claims;
|
||||
|
|
@ -10,6 +11,7 @@ using Microsoft.AspNet.Builder;
|
|||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.TestHost;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.WebEncoders;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication.Twitter
|
||||
|
|
@ -61,6 +63,22 @@ namespace Microsoft.AspNet.Authentication.Twitter
|
|||
Assert.Contains("custom=test", query);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BadSignInWill500()
|
||||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.ConsumerKey = "Test Consumer Key";
|
||||
options.ConsumerSecret = "Test Consumer Secret";
|
||||
});
|
||||
|
||||
// Send a bogus sign in
|
||||
var transaction = await server.SendAsync(
|
||||
"https://example.com/signin-twitter");
|
||||
|
||||
Assert.Equal(HttpStatusCode.InternalServerError, transaction.Response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SignInThrows()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -34,9 +34,9 @@ namespace Microsoft.AspNet.Authroization.Test
|
|||
var combined = AuthorizationPolicy.Combine(options, attributes);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, combined.ActiveAuthenticationSchemes.Count());
|
||||
Assert.True(combined.ActiveAuthenticationSchemes.Contains("dupe"));
|
||||
Assert.True(combined.ActiveAuthenticationSchemes.Contains("roles"));
|
||||
Assert.Equal(2, combined.AuthenticationSchemes.Count());
|
||||
Assert.True(combined.AuthenticationSchemes.Contains("dupe"));
|
||||
Assert.True(combined.AuthenticationSchemes.Contains("roles"));
|
||||
Assert.Equal(4, combined.Requirements.Count());
|
||||
Assert.True(combined.Requirements.Any(r => r is DenyAnonymousAuthorizationRequirement));
|
||||
Assert.Equal(2, combined.Requirements.OfType<ClaimsAuthorizationRequirement>().Count());
|
||||
|
|
@ -59,9 +59,9 @@ namespace Microsoft.AspNet.Authroization.Test
|
|||
var combined = AuthorizationPolicy.Combine(options, attributes);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, combined.ActiveAuthenticationSchemes.Count());
|
||||
Assert.True(combined.ActiveAuthenticationSchemes.Contains("dupe"));
|
||||
Assert.True(combined.ActiveAuthenticationSchemes.Contains("default"));
|
||||
Assert.Equal(2, combined.AuthenticationSchemes.Count());
|
||||
Assert.True(combined.AuthenticationSchemes.Contains("dupe"));
|
||||
Assert.True(combined.AuthenticationSchemes.Contains("default"));
|
||||
Assert.Equal(2, combined.Requirements.Count());
|
||||
Assert.False(combined.Requirements.Any(r => r is DenyAnonymousAuthorizationRequirement));
|
||||
Assert.Equal(2, combined.Requirements.OfType<ClaimsAuthorizationRequirement>().Count());
|
||||
|
|
|
|||
Loading…
Reference in New Issue