Add RemoteAuthenticationHandler base/error handling logic

This commit is contained in:
Hao Kung 2015-10-14 23:08:43 -07:00
parent bfc1fcf421
commit 409b50269a
82 changed files with 1095 additions and 1347 deletions

View File

@ -21,7 +21,7 @@ namespace CookieSample
app.UseCookieAuthentication(options => app.UseCookieAuthentication(options =>
{ {
options.AutomaticAuthentication = true; options.AutomaticAuthenticate = true;
}); });
app.Run(async context => app.Run(async context =>

View File

@ -22,7 +22,7 @@ namespace CookieSessionSample
app.UseCookieAuthentication(options => app.UseCookieAuthentication(options =>
{ {
options.AutomaticAuthentication = true; options.AutomaticAuthenticate = true;
options.SessionStore = new MemoryCacheTicketStore(); options.SessionStore = new MemoryCacheTicketStore();
}); });

View File

@ -23,7 +23,7 @@ namespace OpenIdConnectSample
app.UseCookieAuthentication(options => app.UseCookieAuthentication(options =>
{ {
options.AutomaticAuthentication = true; options.AutomaticAuthenticate = true;
}); });
app.UseOpenIdConnectAuthentication(options => app.UseOpenIdConnectAuthentication(options =>

View File

@ -29,7 +29,8 @@ namespace CookieSample
app.UseCookieAuthentication(options => app.UseCookieAuthentication(options =>
{ {
options.AutomaticAuthentication = true; options.AutomaticAuthenticate = true;
options.AutomaticChallenge = true;
options.LoginPath = new PathString("/login"); options.LoginPath = new PathString("/login");
}); });

View File

@ -101,43 +101,28 @@ namespace Microsoft.AspNet.Authentication.Cookies
return ticket; return ticket;
} }
protected override async Task<AuthenticationTicket> HandleAuthenticateAsync() protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{ {
AuthenticationTicket ticket = null; var ticket = await EnsureCookieTicket();
try if (ticket == null)
{ {
ticket = await EnsureCookieTicket(); return AuthenticateResult.Failed("No ticket.");
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);
} }
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, return AuthenticateResult.Failed("No principal.");
CookieExceptionContext.ExceptionLocation.Authenticate, exception, ticket);
await Options.Events.Exception(exceptionContext);
if (exceptionContext.Rethrow)
{
throw;
}
return exceptionContext.Ticket;
} }
if (context.ShouldRenew)
{
_shouldRenew = true;
}
return AuthenticateResult.Success(new AuthenticationTicket(context.Principal, context.Properties, Options.AuthenticationScheme));
} }
private CookieOptions BuildCookieOptions() private CookieOptions BuildCookieOptions()
@ -167,8 +152,9 @@ namespace Microsoft.AspNet.Authentication.Cookies
return; return;
} }
var ticket = await HandleAuthenticateOnceAsync(); // REVIEW: Should this check if there was an error, and then if that error was already handled??
try var ticket = (await HandleAuthenticateOnceAsync())?.Ticket;
if (ticket != null)
{ {
if (_renewIssuedUtc.HasValue) if (_renewIssuedUtc.HasValue)
{ {
@ -205,141 +191,105 @@ namespace Microsoft.AspNet.Authentication.Cookies
await ApplyHeaders(shouldRedirectToReturnUrl: false); 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) protected override async Task HandleSignInAsync(SignInContext signin)
{ {
var ticket = await EnsureCookieTicket(); 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(); issuedUtc = signInContext.Properties.IssuedUtc.Value;
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);
} }
catch (Exception exception) else
{ {
var exceptionContext = new CookieExceptionContext(Context, Options, issuedUtc = Options.SystemClock.UtcNow;
CookieExceptionContext.ExceptionLocation.SignIn, exception, ticket); signInContext.Properties.IssuedUtc = issuedUtc;
await Options.Events.Exception(exceptionContext);
if (exceptionContext.Rethrow)
{
throw;
}
} }
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) protected override async Task HandleSignOutAsync(SignOutContext signOutContext)
{ {
var ticket = await EnsureCookieTicket(); var ticket = await EnsureCookieTicket();
try var cookieOptions = BuildCookieOptions();
if (Options.SessionStore != null && _sessionKey != null)
{ {
var cookieOptions = BuildCookieOptions(); await Options.SessionStore.RemoveAsync(_sessionKey);
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;
}
} }
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) private async Task ApplyHeaders(bool shouldRedirectToReturnUrl)
@ -374,30 +324,17 @@ namespace Microsoft.AspNet.Authentication.Cookies
return path[0] == '/' && path[1] != '/' && path[1] != '\\'; 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 +
var accessDeniedUri = "://" +
Request.Scheme + Request.Host +
"://" + OriginalPathBase +
Request.Host + Options.AccessDeniedPath;
OriginalPathBase +
Options.AccessDeniedPath;
var redirectContext = new CookieRedirectContext(Context, Options, accessDeniedUri); var redirectContext = new CookieRedirectContext(Context, Options, accessDeniedUri);
await Options.Events.RedirectToAccessDenied(redirectContext); 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;
}
}
return true; return true;
} }
@ -409,28 +346,16 @@ namespace Microsoft.AspNet.Authentication.Cookies
} }
var redirectUri = new AuthenticationProperties(context.Properties).RedirectUri; 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 loginUri = Options.LoginPath + QueryString.Create(Options.ReturnUrlParameter, redirectUri);
var redirectContext = new CookieRedirectContext(Context, Options, BuildRedirectUri(loginUri)); var redirectContext = new CookieRedirectContext(Context, Options, BuildRedirectUri(loginUri));
await Options.Events.RedirectToLogin(redirectContext); 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;
}
}
return true; return true;
} }
} }
} }

View File

@ -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; }
}
}

View File

@ -42,11 +42,6 @@ namespace Microsoft.AspNet.Authentication.Cookies
return Task.FromResult(0); 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> /// <summary>
/// Implements the interface method by invoking the related delegate method. /// Implements the interface method by invoking the related delegate method.
/// </summary> /// </summary>
@ -95,11 +90,5 @@ namespace Microsoft.AspNet.Authentication.Cookies
/// </summary> /// </summary>
/// <param name="context">Contains information about the event</param> /// <param name="context">Contains information about the event</param>
public virtual Task RedirectToAccessDenied(CookieRedirectContext context) => OnRedirect(context); 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);
} }
} }

View File

@ -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; }
}
}

View File

@ -9,7 +9,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
/// <summary> /// <summary>
/// Context passed when a Challenge, SignIn, or SignOut causes a redirect in the cookie middleware /// Context passed when a Challenge, SignIn, or SignOut causes a redirect in the cookie middleware
/// </summary> /// </summary>
public class CookieRedirectContext : BaseContext<CookieAuthenticationOptions> public class CookieRedirectContext : BaseCookieContext
{ {
/// <summary> /// <summary>
/// Creates a new context object. /// Creates a new context object.

View File

@ -10,7 +10,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
/// <summary> /// <summary>
/// Context object passed to the ICookieAuthenticationEvents method SignedIn. /// Context object passed to the ICookieAuthenticationEvents method SignedIn.
/// </summary> /// </summary>
public class CookieSignedInContext : BaseContext<CookieAuthenticationOptions> public class CookieSignedInContext : BaseCookieContext
{ {
/// <summary> /// <summary>
/// Creates a new instance of the context object. /// Creates a new instance of the context object.

View File

@ -10,7 +10,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
/// <summary> /// <summary>
/// Context object passed to the ICookieAuthenticationEvents method SigningIn. /// Context object passed to the ICookieAuthenticationEvents method SigningIn.
/// </summary> /// </summary>
public class CookieSigningInContext : BaseContext<CookieAuthenticationOptions> public class CookieSigningInContext : BaseCookieContext
{ {
/// <summary> /// <summary>
/// Creates a new instance of the context object. /// Creates a new instance of the context object.

View File

@ -8,7 +8,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
/// <summary> /// <summary>
/// Context object passed to the ICookieAuthenticationEvents method SigningOut /// Context object passed to the ICookieAuthenticationEvents method SigningOut
/// </summary> /// </summary>
public class CookieSigningOutContext : BaseContext<CookieAuthenticationOptions> public class CookieSigningOutContext : BaseCookieContext
{ {
/// <summary> /// <summary>
/// ///

View File

@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
/// <summary> /// <summary>
/// Context object passed to the ICookieAuthenticationProvider method ValidatePrincipal. /// Context object passed to the ICookieAuthenticationProvider method ValidatePrincipal.
/// </summary> /// </summary>
public class CookieValidatePrincipalContext : BaseContext<CookieAuthenticationOptions> public class CookieValidatePrincipalContext : BaseCookieContext
{ {
/// <summary> /// <summary>
/// Creates a new instance of the context object. /// Creates a new instance of the context object.

View File

@ -60,11 +60,5 @@ namespace Microsoft.AspNet.Authentication.Cookies
/// </summary> /// </summary>
/// <param name="context">Contains information about the login session as well as information about the authentication cookie.</param> /// <param name="context">Contains information about the login session as well as information about the authentication cookie.</param>
Task SigningOut(CookieSigningOutContext context); 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);
} }
} }

View File

@ -6,7 +6,7 @@ using Microsoft.AspNet.Http;
namespace Microsoft.AspNet.Authentication.JwtBearer namespace Microsoft.AspNet.Authentication.JwtBearer
{ {
public class AuthenticationFailedContext : BaseControlContext<JwtBearerOptions> public class AuthenticationFailedContext : BaseJwtBearerContext
{ {
public AuthenticationFailedContext(HttpContext context, JwtBearerOptions options) public AuthenticationFailedContext(HttpContext context, JwtBearerOptions options)
: base(context, options) : base(context, options)

View File

@ -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; }
}
}

View File

@ -5,7 +5,7 @@ using Microsoft.AspNet.Http;
namespace Microsoft.AspNet.Authentication.JwtBearer namespace Microsoft.AspNet.Authentication.JwtBearer
{ {
public class JwtBearerChallengeContext : BaseControlContext<JwtBearerOptions> public class JwtBearerChallengeContext : BaseJwtBearerContext
{ {
public JwtBearerChallengeContext(HttpContext context, JwtBearerOptions options) public JwtBearerChallengeContext(HttpContext context, JwtBearerOptions options)
: base(context, options) : base(context, options)

View File

@ -5,7 +5,7 @@ using Microsoft.AspNet.Http;
namespace Microsoft.AspNet.Authentication.JwtBearer namespace Microsoft.AspNet.Authentication.JwtBearer
{ {
public class ReceivedTokenContext : BaseControlContext<JwtBearerOptions> public class ReceivedTokenContext : BaseJwtBearerContext
{ {
public ReceivedTokenContext(HttpContext context, JwtBearerOptions options) public ReceivedTokenContext(HttpContext context, JwtBearerOptions options)
: base(context, options) : base(context, options)

View File

@ -5,7 +5,7 @@ using Microsoft.AspNet.Http;
namespace Microsoft.AspNet.Authentication.JwtBearer namespace Microsoft.AspNet.Authentication.JwtBearer
{ {
public class ReceivingTokenContext : BaseControlContext<JwtBearerOptions> public class ReceivingTokenContext : BaseJwtBearerContext
{ {
public ReceivingTokenContext(HttpContext context, JwtBearerOptions options) public ReceivingTokenContext(HttpContext context, JwtBearerOptions options)
: base(context, options) : base(context, options)

View File

@ -5,7 +5,7 @@ using Microsoft.AspNet.Http;
namespace Microsoft.AspNet.Authentication.JwtBearer namespace Microsoft.AspNet.Authentication.JwtBearer
{ {
public class ValidatedTokenContext : BaseControlContext<JwtBearerOptions> public class ValidatedTokenContext : BaseJwtBearerContext
{ {
public ValidatedTokenContext(HttpContext context, JwtBearerOptions options) public ValidatedTokenContext(HttpContext context, JwtBearerOptions options)
: base(context, options) : base(context, options)

View File

@ -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. /// 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> /// </summary>
/// <returns></returns> /// <returns></returns>
protected override async Task<AuthenticationTicket> HandleAuthenticateAsync() protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{ {
string token = null; string token = null;
try try
@ -32,12 +32,12 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
await Options.Events.ReceivingToken(receivingTokenContext); await Options.Events.ReceivingToken(receivingTokenContext);
if (receivingTokenContext.HandledResponse) if (receivingTokenContext.HandledResponse)
{ {
return receivingTokenContext.AuthenticationTicket; return AuthenticateResult.Success(receivingTokenContext.AuthenticationTicket);
} }
if (receivingTokenContext.Skipped) if (receivingTokenContext.Skipped)
{ {
return null; return AuthenticateResult.Success(ticket: null);
} }
// If application retrieved token from somewhere else, use that. // 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 no authorization header found, nothing to process further
if (string.IsNullOrEmpty(authorization)) if (string.IsNullOrEmpty(authorization))
{ {
return null; return AuthenticateResult.Failed("No authorization header.");
} }
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
@ -61,7 +61,7 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
// If no token found, no further work possible // If no token found, no further work possible
if (string.IsNullOrEmpty(token)) 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); await Options.Events.ReceivedToken(receivedTokenContext);
if (receivedTokenContext.HandledResponse) if (receivedTokenContext.HandledResponse)
{ {
return receivedTokenContext.AuthenticationTicket; return AuthenticateResult.Success(receivedTokenContext.AuthenticationTicket);
} }
if (receivedTokenContext.Skipped) if (receivedTokenContext.Skipped)
{ {
return null; return AuthenticateResult.Success(ticket: null);
} }
if (_configuration == null && Options.ConfigurationManager != null) if (_configuration == null && Options.ConfigurationManager != null)
@ -118,18 +118,19 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
await Options.Events.ValidatedToken(validatedTokenContext); await Options.Events.ValidatedToken(validatedTokenContext);
if (validatedTokenContext.HandledResponse) if (validatedTokenContext.HandledResponse)
{ {
return validatedTokenContext.AuthenticationTicket; return AuthenticateResult.Success(validatedTokenContext.AuthenticationTicket);
} }
if (validatedTokenContext.Skipped) 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"); throw new InvalidOperationException("No SecurityTokenValidator available for token: " + token ?? "null");
} }
catch (Exception ex) catch (Exception ex)
@ -150,12 +151,11 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
await Options.Events.AuthenticationFailed(authenticationFailedContext); await Options.Events.AuthenticationFailed(authenticationFailedContext);
if (authenticationFailedContext.HandledResponse) if (authenticationFailedContext.HandledResponse)
{ {
return authenticationFailedContext.AuthenticationTicket; return AuthenticateResult.Success(authenticationFailedContext.AuthenticationTicket);
} }
if (authenticationFailedContext.Skipped) if (authenticationFailedContext.Skipped)
{ {
return null; return AuthenticateResult.Success(ticket: null);
} }
throw; throw;

View File

@ -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;
}
}
}

View File

@ -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));
}
}
}

View File

@ -8,7 +8,7 @@ namespace Microsoft.AspNet.Authentication.OAuth
/// <summary> /// <summary>
/// Specifies callback methods which the <see cref="OAuthMiddleware"/> invokes to enable developer control over the authentication process. /// Specifies callback methods which the <see cref="OAuthMiddleware"/> invokes to enable developer control over the authentication process.
/// </summary> /// </summary>
public interface IOAuthEvents public interface IOAuthEvents : IRemoteAuthenticationEvents
{ {
/// <summary> /// <summary>
/// Invoked after the provider successfully authenticates a user. This can be used to retrieve user information. /// 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> /// <returns>A <see cref="Task"/> representing the completed operation.</returns>
Task CreatingTicket(OAuthCreatingTicketContext context); 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> /// <summary>
/// Called when a Challenge causes a redirect to the authorize endpoint. /// Called when a Challenge causes a redirect to the authorize endpoint.
/// </summary> /// </summary>

View File

@ -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; }
}
}

View File

@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Authentication.OAuth
/// <summary> /// <summary>
/// Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>. /// Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.
/// </summary> /// </summary>
public class OAuthCreatingTicketContext : BaseContext<OAuthOptions> public class OAuthCreatingTicketContext : BaseContext
{ {
/// <summary> /// <summary>
/// Initializes a new <see cref="OAuthCreatingTicketContext"/>. /// Initializes a new <see cref="OAuthCreatingTicketContext"/>.
@ -46,7 +46,7 @@ namespace Microsoft.AspNet.Authentication.OAuth
HttpClient backchannel, HttpClient backchannel,
OAuthTokenResponse tokens, OAuthTokenResponse tokens,
JObject user) JObject user)
: base(context, options) : base(context)
{ {
if (context == null) if (context == null)
{ {
@ -76,8 +76,11 @@ namespace Microsoft.AspNet.Authentication.OAuth
TokenResponse = tokens; TokenResponse = tokens;
Backchannel = backchannel; Backchannel = backchannel;
User = user; User = user;
Options = options;
} }
public OAuthOptions Options { get; }
/// <summary> /// <summary>
/// Gets the JSON-serialized user or an empty /// Gets the JSON-serialized user or an empty
/// <see cref="JObject"/> if it is not available. /// <see cref="JObject"/> if it is not available.

View File

@ -9,18 +9,13 @@ namespace Microsoft.AspNet.Authentication.OAuth
/// <summary> /// <summary>
/// Default <see cref="IOAuthEvents"/> implementation. /// Default <see cref="IOAuthEvents"/> implementation.
/// </summary> /// </summary>
public class OAuthEvents : IOAuthEvents public class OAuthEvents : RemoteAuthenticationEvents, IOAuthEvents
{ {
/// <summary> /// <summary>
/// Gets or sets the function that is invoked when the CreatingTicket method is invoked. /// Gets or sets the function that is invoked when the CreatingTicket method is invoked.
/// </summary> /// </summary>
public Func<OAuthCreatingTicketContext, Task> OnCreatingTicket { get; set; } = context => Task.FromResult(0); 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> /// <summary>
/// Gets or sets the delegate that is invoked when the RedirectToAuthorizationEndpoint method is invoked. /// Gets or sets the delegate that is invoked when the RedirectToAuthorizationEndpoint method is invoked.
/// </summary> /// </summary>
@ -37,17 +32,10 @@ namespace Microsoft.AspNet.Authentication.OAuth
/// <returns>A <see cref="Task"/> representing the completed operation.</returns> /// <returns>A <see cref="Task"/> representing the completed operation.</returns>
public virtual Task CreatingTicket(OAuthCreatingTicketContext context) => OnCreatingTicket(context); 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> /// <summary>
/// Called when a Challenge causes a redirect to authorize endpoint in the OAuth middleware. /// Called when a Challenge causes a redirect to authorize endpoint in the OAuth middleware.
/// </summary> /// </summary>
/// <param name="context">Contains redirect URI and <see cref="AuthenticationProperties"/> of the challenge.</param> /// <param name="context">Contains redirect URI and <see cref="AuthenticationProperties"/> of the challenge.</param>
public virtual Task RedirectToAuthorizationEndpoint(OAuthRedirectToAuthorizationContext context) => OnRedirectToAuthorizationEndpoint(context); public virtual Task RedirectToAuthorizationEndpoint(OAuthRedirectToAuthorizationContext context) => OnRedirectToAuthorizationEndpoint(context);
} }
} }

View File

@ -9,7 +9,7 @@ namespace Microsoft.AspNet.Authentication.OAuth
/// <summary> /// <summary>
/// Context passed when a Challenge causes a redirect to authorize endpoint in the middleware. /// Context passed when a Challenge causes a redirect to authorize endpoint in the middleware.
/// </summary> /// </summary>
public class OAuthRedirectToAuthorizationContext : BaseContext<OAuthOptions> public class OAuthRedirectToAuthorizationContext : BaseContext
{ {
/// <summary> /// <summary>
/// Creates a new context object. /// 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="properties">The authentication properties of the challenge.</param>
/// <param name="redirectUri">The initial redirect URI.</param> /// <param name="redirectUri">The initial redirect URI.</param>
public OAuthRedirectToAuthorizationContext(HttpContext context, OAuthOptions options, AuthenticationProperties properties, string redirectUri) public OAuthRedirectToAuthorizationContext(HttpContext context, OAuthOptions options, AuthenticationProperties properties, string redirectUri)
: base(context, options) : base(context)
{ {
RedirectUri = redirectUri; RedirectUri = redirectUri;
Properties = properties; Properties = properties;
Options = options;
} }
public OAuthOptions Options { get; }
/// <summary> /// <summary>
/// Gets the URI used for the redirect operation. /// Gets the URI used for the redirect operation.
/// </summary> /// </summary>

View File

@ -12,14 +12,13 @@ using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication; using Microsoft.AspNet.Http.Authentication;
using Microsoft.AspNet.Http.Extensions; using Microsoft.AspNet.Http.Extensions;
using Microsoft.AspNet.Http.Features.Authentication; using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.AspNet.WebUtilities;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace Microsoft.AspNet.Authentication.OAuth 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(); private static readonly RandomNumberGenerator CryptoRandom = RandomNumberGenerator.Create();
@ -30,131 +29,71 @@ namespace Microsoft.AspNet.Authentication.OAuth
protected HttpClient Backchannel { get; private set; } protected HttpClient Backchannel { get; private set; }
public override async Task<bool> InvokeAsync() protected override async Task<AuthenticateResult> HandleRemoteAuthenticateAsync()
{
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()
{ {
AuthenticationProperties properties = null; 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 code = query["code"];
var value = query["error"]; var state = query["state"];
if (!StringValues.IsNullOrEmpty(value))
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); identity.AddClaim(new Claim("refresh_token", tokens.RefreshToken,
// 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,
ClaimValueTypes.String, Options.ClaimsIssuer)); 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); if (!string.IsNullOrEmpty(tokens.TokenType))
} {
catch (Exception ex) identity.AddClaim(new Claim("token_type", tokens.TokenType,
{ ClaimValueTypes.String, Options.ClaimsIssuer));
Logger.LogError("Authentication failed", ex); }
return new AuthenticationTicket(properties, Options.AuthenticationScheme);
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) 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); var response = await Backchannel.SendAsync(requestMessage, Context.RequestAborted);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
var payload = JObject.Parse(await response.Content.ReadAsStringAsync()); var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
return new OAuthTokenResponse(payload); return new OAuthTokenResponse(payload);
} }
@ -215,7 +153,6 @@ namespace Microsoft.AspNet.Authentication.OAuth
GenerateCorrelationId(properties); GenerateCorrelationId(properties);
var authorizationEndpoint = BuildChallengeUrl(properties, BuildRedirectUri(Options.CallbackPath)); var authorizationEndpoint = BuildChallengeUrl(properties, BuildRedirectUri(Options.CallbackPath));
var redirectContext = new OAuthRedirectToAuthorizationContext( var redirectContext = new OAuthRedirectToAuthorizationContext(
Context, Options, Context, Options,
properties, authorizationEndpoint); properties, authorizationEndpoint);
@ -223,21 +160,6 @@ namespace Microsoft.AspNet.Authentication.OAuth
return true; 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) protected virtual string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri)
{ {
var scope = FormatScope(); var scope = FormatScope();

View File

@ -91,6 +91,11 @@ namespace Microsoft.AspNet.Authentication.OAuth
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.TokenEndpoint))); 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) if (Options.Events == null)
{ {
Options.Events = new OAuthEvents(); Options.Events = new OAuthEvents();
@ -112,6 +117,10 @@ namespace Microsoft.AspNet.Authentication.OAuth
{ {
Options.SignInScheme = sharedOptions.Value.SignInScheme; 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; } protected HttpClient Backchannel { get; private set; }

View File

@ -12,8 +12,13 @@ namespace Microsoft.AspNet.Authentication.OAuth
/// <summary> /// <summary>
/// Configuration options for <see cref="OAuthMiddleware"/>. /// Configuration options for <see cref="OAuthMiddleware"/>.
/// </summary> /// </summary>
public class OAuthOptions : AuthenticationOptions public class OAuthOptions : RemoteAuthenticationOptions
{ {
public OAuthOptions()
{
Events = new OAuthEvents();
}
/// <summary> /// <summary>
/// Gets or sets the provider-assigned client id. /// Gets or sets the provider-assigned client id.
/// </summary> /// </summary>
@ -41,54 +46,20 @@ namespace Microsoft.AspNet.Authentication.OAuth
/// </summary> /// </summary>
public string UserInformationEndpoint { get; set; } 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> /// <summary>
/// Gets or sets the <see cref="IOAuthEvents"/> used to handle authentication events. /// Gets or sets the <see cref="IOAuthEvents"/> used to handle authentication events.
/// </summary> /// </summary>
public IOAuthEvents Events { get; set; } = new OAuthEvents(); public new IOAuthEvents Events
{
get { return (IOAuthEvents)base.Events; }
set { base.Events = value; }
}
/// <summary> /// <summary>
/// A list of permissions to request. /// A list of permissions to request.
/// </summary> /// </summary>
public IList<string> Scope { get; } = new List<string>(); 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> /// <summary>
/// Gets or sets the type used to secure data handled by the middleware. /// Gets or sets the type used to secure data handled by the middleware.
/// </summary> /// </summary>

View File

@ -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)
{
}
}
}

View File

@ -7,7 +7,7 @@ using Microsoft.IdentityModel.Protocols.OpenIdConnect;
namespace Microsoft.AspNet.Authentication.OpenIdConnect namespace Microsoft.AspNet.Authentication.OpenIdConnect
{ {
public class AuthenticationFailedContext : BaseControlContext<OpenIdConnectOptions> public class AuthenticationFailedContext : BaseOpenIdConnectContext
{ {
public AuthenticationFailedContext(HttpContext context, OpenIdConnectOptions options) public AuthenticationFailedContext(HttpContext context, OpenIdConnectOptions options)
: base(context, options) : base(context, options)
@ -15,7 +15,5 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
} }
public Exception Exception { get; set; } public Exception Exception { get; set; }
public OpenIdConnectMessage ProtocolMessage { get; set; }
} }
} }

View File

@ -6,15 +6,13 @@ using Microsoft.IdentityModel.Protocols.OpenIdConnect;
namespace Microsoft.AspNet.Authentication.OpenIdConnect namespace Microsoft.AspNet.Authentication.OpenIdConnect
{ {
public class AuthenticationValidatedContext : BaseControlContext<OpenIdConnectOptions> public class AuthenticationValidatedContext : BaseOpenIdConnectContext
{ {
public AuthenticationValidatedContext(HttpContext context, OpenIdConnectOptions options) public AuthenticationValidatedContext(HttpContext context, OpenIdConnectOptions options)
: base(context, options) : base(context, options)
{ {
} }
public OpenIdConnectMessage ProtocolMessage { get; set; }
public OpenIdConnectTokenEndpointResponse TokenEndpointResponse { get; set; } public OpenIdConnectTokenEndpointResponse TokenEndpointResponse { get; set; }
} }
} }

View File

@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// <summary> /// <summary>
/// This Context can be used to be informed when an 'AuthorizationCode' is received over the OpenIdConnect protocol. /// This Context can be used to be informed when an 'AuthorizationCode' is received over the OpenIdConnect protocol.
/// </summary> /// </summary>
public class AuthorizationCodeReceivedContext : BaseControlContext<OpenIdConnectOptions> public class AuthorizationCodeReceivedContext : BaseOpenIdConnectContext
{ {
/// <summary> /// <summary>
/// Creates a <see cref="AuthorizationCodeReceivedContext"/> /// Creates a <see cref="AuthorizationCodeReceivedContext"/>
@ -31,11 +31,6 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// </summary> /// </summary>
public JwtSecurityToken JwtSecurityToken { get; set; } public JwtSecurityToken JwtSecurityToken { get; set; }
/// <summary>
/// Gets or sets the <see cref="OpenIdConnectMessage"/>.
/// </summary>
public OpenIdConnectMessage ProtocolMessage { get; set; }
/// <summary> /// <summary>
/// Gets or sets the 'redirect_uri'. /// Gets or sets the 'redirect_uri'.
/// </summary> /// </summary>

View File

@ -7,15 +7,13 @@ using Microsoft.IdentityModel.Protocols.OpenIdConnect;
namespace Microsoft.AspNet.Authentication.OpenIdConnect namespace Microsoft.AspNet.Authentication.OpenIdConnect
{ {
public class AuthorizationResponseReceivedContext : BaseControlContext<OpenIdConnectOptions> public class AuthorizationResponseReceivedContext : BaseOpenIdConnectContext
{ {
public AuthorizationResponseReceivedContext(HttpContext context, OpenIdConnectOptions options) public AuthorizationResponseReceivedContext(HttpContext context, OpenIdConnectOptions options)
: base(context, options) : base(context, options)
{ {
} }
public OpenIdConnectMessage ProtocolMessage { get; set; }
public AuthenticationProperties Properties { get; set; } public AuthenticationProperties Properties { get; set; }
} }
} }

View File

@ -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; }
}
}

View File

@ -8,13 +8,8 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// <summary> /// <summary>
/// Specifies events which the <see cref="OpenIdConnectMiddleware" />invokes to enable developer control over the authentication process. /// Specifies events which the <see cref="OpenIdConnectMiddleware" />invokes to enable developer control over the authentication process.
/// </summary> /// </summary>
public interface IOpenIdConnectEvents public interface IOpenIdConnectEvents : IRemoteAuthenticationEvents
{ {
/// <summary>
/// Invoked when the authentication process completes.
/// </summary>
Task AuthenticationCompleted(AuthenticationCompletedContext context);
/// <summary> /// <summary>
/// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed. /// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed.
/// </summary> /// </summary>

View File

@ -6,15 +6,13 @@ using Microsoft.IdentityModel.Protocols.OpenIdConnect;
namespace Microsoft.AspNet.Authentication.OpenIdConnect namespace Microsoft.AspNet.Authentication.OpenIdConnect
{ {
public class MessageReceivedContext : BaseControlContext<OpenIdConnectOptions> public class MessageReceivedContext : BaseOpenIdConnectContext
{ {
public MessageReceivedContext(HttpContext context, OpenIdConnectOptions options) public MessageReceivedContext(HttpContext context, OpenIdConnectOptions options)
: base(context, options) : base(context, options)
{ {
} }
public OpenIdConnectMessage ProtocolMessage { get; set; }
/// <summary> /// <summary>
/// Bearer Token. This will give application an opportunity to retrieve token from an alternation location. /// Bearer Token. This will give application an opportunity to retrieve token from an alternation location.
/// </summary> /// </summary>

View File

@ -9,13 +9,8 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// <summary> /// <summary>
/// Specifies events which the <see cref="OpenIdConnectMiddleware" />invokes to enable developer control over the authentication process. /// Specifies events which the <see cref="OpenIdConnectMiddleware" />invokes to enable developer control over the authentication process.
/// </summary> /// </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> /// <summary>
/// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed. /// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed.
/// </summary> /// </summary>
@ -61,8 +56,6 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// </summary> /// </summary>
public Func<UserInformationReceivedContext, Task> OnUserInformationReceived { get; set; } = context => Task.FromResult(0); 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 AuthenticationFailed(AuthenticationFailedContext context) => OnAuthenticationFailed(context);
public virtual Task AuthenticationValidated(AuthenticationValidatedContext context) => OnAuthenticationValidated(context); public virtual Task AuthenticationValidated(AuthenticationValidatedContext context) => OnAuthenticationValidated(context);

View File

@ -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 /// 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. /// an instance of <see cref="RedirectContext"/> is passed to the 'RedirectToAuthenticationEndpoint' or 'RedirectToEndSessionEndpoint' events.
/// </summary> /// </summary>
public class RedirectContext : BaseControlContext<OpenIdConnectOptions> public class RedirectContext : BaseOpenIdConnectContext
{ {
public RedirectContext(HttpContext context, OpenIdConnectOptions options) public RedirectContext(HttpContext context, OpenIdConnectOptions options)
: base(context, options) : base(context, options)
{ {
} }
/// <summary>
/// Gets or sets the <see cref="OpenIdConnectMessage"/>.
/// </summary>
public OpenIdConnectMessage ProtocolMessage { get; set; }
} }
} }

View File

@ -6,7 +6,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// <summary> /// <summary>
/// This Context can be used to be informed when an 'AuthorizationCode' is redeemed for tokens at the token endpoint. /// This Context can be used to be informed when an 'AuthorizationCode' is redeemed for tokens at the token endpoint.
/// </summary> /// </summary>
public class TokenResponseReceivedContext : BaseControlContext<OpenIdConnectOptions> public class TokenResponseReceivedContext : BaseOpenIdConnectContext
{ {
/// <summary> /// <summary>
/// Creates a <see cref="TokenResponseReceivedContext"/> /// 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. /// Gets or sets the <see cref="OpenIdConnectTokenEndpointResponse"/> that contains the tokens and json response received after redeeming the code at the token endpoint.
/// </summary> /// </summary>
public OpenIdConnectTokenEndpointResponse TokenEndpointResponse { get; set; } public OpenIdConnectTokenEndpointResponse TokenEndpointResponse { get; set; }
/// <summary>
/// Gets or sets the <see cref="OpenIdConnectMessage"/>.
/// </summary>
public OpenIdConnectMessage ProtocolMessage { get; set; }
} }
} }

View File

@ -7,15 +7,13 @@ using Newtonsoft.Json.Linq;
namespace Microsoft.AspNet.Authentication.OpenIdConnect namespace Microsoft.AspNet.Authentication.OpenIdConnect
{ {
public class UserInformationReceivedContext : BaseControlContext<OpenIdConnectOptions> public class UserInformationReceivedContext : BaseOpenIdConnectContext
{ {
public UserInformationReceivedContext(HttpContext context, OpenIdConnectOptions options) public UserInformationReceivedContext(HttpContext context, OpenIdConnectOptions options)
: base(context, options) : base(context, options)
{ {
} }
public OpenIdConnectMessage ProtocolMessage { get; set; }
public JObject User { get; set; } public JObject User { get; set; }
} }
} }

View File

@ -26,7 +26,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// <summary> /// <summary>
/// A per-request authentication handler for the OpenIdConnectAuthenticationMiddleware. /// A per-request authentication handler for the OpenIdConnectAuthenticationMiddleware.
/// </summary> /// </summary>
public class OpenIdConnectHandler : AuthenticationHandler<OpenIdConnectOptions> public class OpenIdConnectHandler : RemoteAuthenticationHandler<OpenIdConnectOptions>
{ {
private const string NonceProperty = "N"; private const string NonceProperty = "N";
private const string UriSchemeDelimiter = "://"; private const string UriSchemeDelimiter = "://";
@ -263,7 +263,6 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
} }
Response.Redirect(redirectUri); Response.Redirect(redirectUri);
return true; return true;
} }
else if (Options.AuthenticationMethod == OpenIdConnectRedirectBehavior.FormPost) else if (Options.AuthenticationMethod == OpenIdConnectRedirectBehavior.FormPost)
@ -292,12 +291,10 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
Response.Headers[HeaderNames.Expires] = "-1"; Response.Headers[HeaderNames.Expires] = "-1";
await Response.Body.WriteAsync(buffer, 0, buffer.Length); await Response.Body.WriteAsync(buffer, 0, buffer.Length);
return true; return true;
} }
Logger.LogError("An unsupported authentication method has been configured: {0}", Options.AuthenticationMethod); throw new NotImplementedException($"An unsupported authentication method has been configured: {Options.AuthenticationMethod}");
return false;
} }
/// <summary> /// <summary>
@ -305,16 +302,10 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// </summary> /// </summary>
/// <returns>An <see cref="AuthenticationTicket"/> if successful.</returns> /// <returns>An <see cref="AuthenticationTicket"/> if successful.</returns>
/// <remarks>Uses log id's OIDCH-0000 - OIDCH-0025</remarks> /// <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()); 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; OpenIdConnectMessage message = null;
if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase)) 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 // See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Security
if (!string.IsNullOrEmpty(message.IdToken) || !string.IsNullOrEmpty(message.AccessToken)) if (!string.IsNullOrEmpty(message.IdToken) || !string.IsNullOrEmpty(message.AccessToken))
{ {
Logger.LogError("An OpenID Connect response cannot contain an identity token " + return AuthenticateResult.Failed("An OpenID Connect response cannot contain an " +
"or an access token when using response_mode=query"); "identity token or an access token when using response_mode=query");
return null;
} }
} }
// assumption: if the ContentType is "application/x-www-form-urlencoded" it should be safe to read as it is small. // 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) if (message == null)
{ {
return null; return AuthenticateResult.Failed("No message.");
} }
try try
@ -352,11 +342,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
var messageReceivedContext = await RunMessageReceivedEventAsync(message); var messageReceivedContext = await RunMessageReceivedEventAsync(message);
if (messageReceivedContext.HandledResponse) if (messageReceivedContext.HandledResponse)
{ {
return messageReceivedContext.AuthenticationTicket; return AuthenticateResult.Success(messageReceivedContext.AuthenticationTicket);
} }
else if (messageReceivedContext.Skipped) else if (messageReceivedContext.Skipped)
{ {
return null; return AuthenticateResult.Success(ticket: null);
} }
message = messageReceivedContext.ProtocolMessage; 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. // This wasn't a valid ODIC message, it may not have been intended for us.
Logger.LogVerbose(Resources.OIDCH_0004_MessageStateIsNullOrEmpty); 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. // 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) if (properties == null)
{ {
Logger.LogError(Resources.OIDCH_0005_MessageStateIsInvalid); 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 any of the error fields are set, throw error null
if (!string.IsNullOrEmpty(message.Error)) 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"); 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; string userstate = null;
properties.Items.TryGetValue(OpenIdConnectDefaults.UserstatePropertiesKey, out userstate); properties.Items.TryGetValue(OpenIdConnectDefaults.UserstatePropertiesKey, out userstate);
message.State = userstate; message.State = userstate;
if (!ValidateCorrelationId(properties)) if (!ValidateCorrelationId(properties))
{ {
return null; return AuthenticateResult.Failed("Correlation failed.");
} }
if (_configuration == null && Options.ConfigurationManager != null) if (_configuration == null && Options.ConfigurationManager != null)
@ -409,12 +399,12 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
if (authorizationResponseReceivedContext.HandledResponse) if (authorizationResponseReceivedContext.HandledResponse)
{ {
Logger.LogVerbose("AuthorizationResponseReceived.HandledResponse"); Logger.LogVerbose("AuthorizationResponseReceived.HandledResponse");
return authorizationResponseReceivedContext.AuthenticationTicket; return AuthenticateResult.Success(authorizationResponseReceivedContext.AuthenticationTicket);
} }
else if (authorizationResponseReceivedContext.Skipped) else if (authorizationResponseReceivedContext.Skipped)
{ {
Logger.LogVerbose("AuthorizationResponseReceived.Skipped"); Logger.LogVerbose("AuthorizationResponseReceived.Skipped");
return null; return AuthenticateResult.Success(ticket: null);
} }
message = authorizationResponseReceivedContext.ProtocolMessage; message = authorizationResponseReceivedContext.ProtocolMessage;
properties = authorizationResponseReceivedContext.Properties; properties = authorizationResponseReceivedContext.Properties;
@ -430,7 +420,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
else else
{ {
Logger.LogDebug(Resources.OIDCH_0045_Id_Token_Code_Missing); Logger.LogDebug(Resources.OIDCH_0045_Id_Token_Code_Missing);
return null; return AuthenticateResult.Failed(Resources.OIDCH_0045_Id_Token_Code_Missing);
} }
} }
catch (Exception exception) catch (Exception exception)
@ -450,11 +440,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
var authenticationFailedContext = await RunAuthenticationFailedEventAsync(message, exception); var authenticationFailedContext = await RunAuthenticationFailedEventAsync(message, exception);
if (authenticationFailedContext.HandledResponse) if (authenticationFailedContext.HandledResponse)
{ {
return authenticationFailedContext.AuthenticationTicket; return AuthenticateResult.Success(authenticationFailedContext.AuthenticationTicket);
} }
else if (authenticationFailedContext.Skipped) else if (authenticationFailedContext.Skipped)
{ {
return null; return AuthenticateResult.Success(ticket: null);
} }
throw; throw;
@ -462,7 +452,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
} }
// Authorization Code Flow // Authorization Code Flow
private async Task<AuthenticationTicket> HandleCodeOnlyFlow(OpenIdConnectMessage message, AuthenticationProperties properties) private async Task<AuthenticateResult> HandleCodeOnlyFlow(OpenIdConnectMessage message, AuthenticationProperties properties)
{ {
AuthenticationTicket ticket = null; AuthenticationTicket ticket = null;
JwtSecurityToken jwt = null; JwtSecurityToken jwt = null;
@ -476,11 +466,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
var authorizationCodeReceivedContext = await RunAuthorizationCodeReceivedEventAsync(message, properties, ticket, jwt); var authorizationCodeReceivedContext = await RunAuthorizationCodeReceivedEventAsync(message, properties, ticket, jwt);
if (authorizationCodeReceivedContext.HandledResponse) if (authorizationCodeReceivedContext.HandledResponse)
{ {
return authorizationCodeReceivedContext.AuthenticationTicket; return AuthenticateResult.Success(authorizationCodeReceivedContext.AuthenticationTicket);
} }
else if (authorizationCodeReceivedContext.Skipped) else if (authorizationCodeReceivedContext.Skipped)
{ {
return null; return AuthenticateResult.Success(ticket: null);
} }
message = authorizationCodeReceivedContext.ProtocolMessage; message = authorizationCodeReceivedContext.ProtocolMessage;
var code = authorizationCodeReceivedContext.Code; var code = authorizationCodeReceivedContext.Code;
@ -493,11 +483,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
var authorizationCodeRedeemedContext = await RunTokenResponseReceivedEventAsync(message, tokenEndpointResponse); var authorizationCodeRedeemedContext = await RunTokenResponseReceivedEventAsync(message, tokenEndpointResponse);
if (authorizationCodeRedeemedContext.HandledResponse) if (authorizationCodeRedeemedContext.HandledResponse)
{ {
return authorizationCodeRedeemedContext.AuthenticationTicket; return AuthenticateResult.Success(authorizationCodeRedeemedContext.AuthenticationTicket);
} }
else if (authorizationCodeRedeemedContext.Skipped) else if (authorizationCodeRedeemedContext.Skipped)
{ {
return null; return AuthenticateResult.Success(ticket: null);
} }
message = authorizationCodeRedeemedContext.ProtocolMessage; message = authorizationCodeRedeemedContext.ProtocolMessage;
@ -526,11 +516,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
var authenticationValidatedContext = await RunAuthenticationValidatedEventAsync(message, ticket, tokenEndpointResponse); var authenticationValidatedContext = await RunAuthenticationValidatedEventAsync(message, ticket, tokenEndpointResponse);
if (authenticationValidatedContext.HandledResponse) if (authenticationValidatedContext.HandledResponse)
{ {
return authenticationValidatedContext.AuthenticationTicket; return AuthenticateResult.Success(authenticationValidatedContext.AuthenticationTicket);
} }
else if (authenticationValidatedContext.Skipped) else if (authenticationValidatedContext.Skipped)
{ {
return null; return AuthenticateResult.Success(ticket: null);
} }
ticket = authenticationValidatedContext.AuthenticationTicket; ticket = authenticationValidatedContext.AuthenticationTicket;
@ -546,11 +536,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
ticket = await GetUserInformationAsync(tokenEndpointResponse.ProtocolMessage, jwt, ticket); ticket = await GetUserInformationAsync(tokenEndpointResponse.ProtocolMessage, jwt, ticket);
} }
return ticket; return AuthenticateResult.Success(ticket);
} }
// Implicit Flow or Hybrid Flow // 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); 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); var authenticationValidatedContext = await RunAuthenticationValidatedEventAsync(message, ticket, tokenEndpointResponse: null);
if (authenticationValidatedContext.HandledResponse) if (authenticationValidatedContext.HandledResponse)
{ {
return authenticationValidatedContext.AuthenticationTicket; return AuthenticateResult.Success(authenticationValidatedContext.AuthenticationTicket);
} }
else if (authenticationValidatedContext.Skipped) else if (authenticationValidatedContext.Skipped)
{ {
return null; return AuthenticateResult.Success(ticket: null);
} }
message = authenticationValidatedContext.ProtocolMessage; message = authenticationValidatedContext.ProtocolMessage;
ticket = authenticationValidatedContext.AuthenticationTicket; ticket = authenticationValidatedContext.AuthenticationTicket;
@ -590,11 +580,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
var authorizationCodeReceivedContext = await RunAuthorizationCodeReceivedEventAsync(message, properties, ticket, jwt); var authorizationCodeReceivedContext = await RunAuthorizationCodeReceivedEventAsync(message, properties, ticket, jwt);
if (authorizationCodeReceivedContext.HandledResponse) if (authorizationCodeReceivedContext.HandledResponse)
{ {
return authorizationCodeReceivedContext.AuthenticationTicket; return AuthenticateResult.Success(authorizationCodeReceivedContext.AuthenticationTicket);
} }
else if (authorizationCodeReceivedContext.Skipped) else if (authorizationCodeReceivedContext.Skipped)
{ {
return null; return AuthenticateResult.Success(ticket: null);
} }
message = authorizationCodeReceivedContext.ProtocolMessage; message = authorizationCodeReceivedContext.ProtocolMessage;
ticket = authorizationCodeReceivedContext.AuthenticationTicket; ticket = authorizationCodeReceivedContext.AuthenticationTicket;
@ -619,7 +609,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
} }
} }
return ticket; return AuthenticateResult.Success(ticket);
} }
/// <summary> /// <summary>
@ -1135,54 +1125,5 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
return ticket; 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;
}
} }
} }

View File

@ -77,10 +77,19 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
throw new ArgumentNullException(nameof(options)); 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; Options.SignInScheme = sharedOptions.Value.SignInScheme;
} }
if (string.IsNullOrEmpty(Options.SignInScheme))
{
throw new ArgumentException("Options.SignInScheme is required.");
}
if (Options.HtmlEncoder == null) if (Options.HtmlEncoder == null)
{ {

View File

@ -6,8 +6,6 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IdentityModel.Tokens; using System.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using System.Net.Http;
using System.Security.Claims;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication; using Microsoft.AspNet.Http.Authentication;
using Microsoft.Extensions.WebEncoders; using Microsoft.Extensions.WebEncoders;
@ -19,7 +17,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// <summary> /// <summary>
/// Configuration options for <see cref="OpenIdConnectOptions"/> /// Configuration options for <see cref="OpenIdConnectOptions"/>
/// </summary> /// </summary>
public class OpenIdConnectOptions : AuthenticationOptions public class OpenIdConnectOptions : RemoteAuthenticationOptions
{ {
/// <summary> /// <summary>
/// Initializes a new <see cref="OpenIdConnectOptions"/> /// Initializes a new <see cref="OpenIdConnectOptions"/>
@ -50,6 +48,8 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
{ {
AuthenticationScheme = authenticationScheme; AuthenticationScheme = authenticationScheme;
DisplayName = OpenIdConnectDefaults.Caption; DisplayName = OpenIdConnectDefaults.Caption;
CallbackPath = new PathString("/signin-oidc");
Events = new OpenIdConnectEvents();
} }
/// <summary> /// <summary>
@ -65,36 +65,6 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// </summary> /// </summary>
public string Authority { get; set; } 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> /// <summary>
/// Gets or sets the 'client_id'. /// Gets or sets the 'client_id'.
/// </summary> /// </summary>
@ -136,7 +106,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// <summary> /// <summary>
/// Gets or sets the <see cref="IOpenIdConnectEvents"/> to notify when processing OpenIdConnect messages. /// Gets or sets the <see cref="IOpenIdConnectEvents"/> to notify when processing OpenIdConnect messages.
/// </summary> /// </summary>
public IOpenIdConnectEvents Events { get; set; } = new OpenIdConnectEvents(); public new IOpenIdConnectEvents Events
{
get { return (IOpenIdConnectEvents)base.Events; }
set { base.Events = value; }
}
/// <summary> /// <summary>
/// Gets or sets the <see cref="OpenIdConnectProtocolValidator"/> that is used to ensure that the 'id_token' received /// 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> /// </summary>
public IList<string> Scope { get; } = new List<string> { "openid", "profile" }; 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> /// <summary>
/// Gets or sets the type used to secure data handled by the middleware. /// Gets or sets the type used to secure data handled by the middleware.
/// </summary> /// </summary>

View File

@ -107,7 +107,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
} }
/// <summary> /// <summary>
/// OIDCH_0029: ChallengeContext == null AND !Options.AutomaticAuthentication /// OIDCH_0029: ChallengeContext == null AND !Options.AutomaticAuthenticate
/// </summary> /// </summary>
internal static string OIDCH_0029_ChallengContextEqualsNull internal static string OIDCH_0029_ChallengContextEqualsNull
{ {
@ -115,7 +115,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
} }
/// <summary> /// <summary>
/// OIDCH_0029: ChallengeContext == null AND !Options.AutomaticAuthentication /// OIDCH_0029: ChallengeContext == null AND !Options.AutomaticAuthenticate
/// </summary> /// </summary>
internal static string FormatOIDCH_0029_ChallengContextEqualsNull() internal static string FormatOIDCH_0029_ChallengContextEqualsNull()
{ {

View File

@ -136,7 +136,7 @@
<value>OIDCH_0028: Response.StatusCode != 401, StatusCode: '{0}'.</value> <value>OIDCH_0028: Response.StatusCode != 401, StatusCode: '{0}'.</value>
</data> </data>
<data name="OIDCH_0029_ChallengContextEqualsNull" xml:space="preserve"> <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>
<data name="OIDCH_0030_Using_Properties_RedirectUri" xml:space="preserve"> <data name="OIDCH_0030_Using_Properties_RedirectUri" xml:space="preserve">
<value>OIDCH_0030: Using properties.RedirectUri for 'local redirect' post authentication: '{0}'.</value> <value>OIDCH_0030: Using properties.RedirectUri for 'local redirect' post authentication: '{0}'.</value>

View File

@ -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; }
}
}

View File

@ -8,7 +8,7 @@ namespace Microsoft.AspNet.Authentication.Twitter
/// <summary> /// <summary>
/// Specifies callback methods which the <see cref="TwitterMiddleware"></see> invokes to enable developer control over the authentication process. /> /// Specifies callback methods which the <see cref="TwitterMiddleware"></see> invokes to enable developer control over the authentication process. />
/// </summary> /// </summary>
public interface ITwitterEvents public interface ITwitterEvents : IRemoteAuthenticationEvents
{ {
/// <summary> /// <summary>
/// Invoked whenever Twitter succesfully authenticates a user /// 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> /// <returns>A <see cref="Task"/> representing the completed operation.</returns>
Task CreatingTicket(TwitterCreatingTicketContext context); 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> /// <summary>
/// Called when a Challenge causes a redirect to authorize endpoint in the Twitter middleware /// Called when a Challenge causes a redirect to authorize endpoint in the Twitter middleware
/// </summary> /// </summary>

View File

@ -10,7 +10,7 @@ namespace Microsoft.AspNet.Authentication.Twitter
/// <summary> /// <summary>
/// Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>. /// Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.
/// </summary> /// </summary>
public class TwitterCreatingTicketContext : BaseContext public class TwitterCreatingTicketContext : BaseTwitterContext
{ {
/// <summary> /// <summary>
/// Initializes a <see cref="TwitterCreatingTicketContext"/> /// Initializes a <see cref="TwitterCreatingTicketContext"/>
@ -22,11 +22,12 @@ namespace Microsoft.AspNet.Authentication.Twitter
/// <param name="accessTokenSecret">Twitter access token secret</param> /// <param name="accessTokenSecret">Twitter access token secret</param>
public TwitterCreatingTicketContext( public TwitterCreatingTicketContext(
HttpContext context, HttpContext context,
TwitterOptions options,
string userId, string userId,
string screenName, string screenName,
string accessToken, string accessToken,
string accessTokenSecret) string accessTokenSecret)
: base(context) : base(context, options)
{ {
UserId = userId; UserId = userId;
ScreenName = screenName; ScreenName = screenName;

View File

@ -9,18 +9,13 @@ namespace Microsoft.AspNet.Authentication.Twitter
/// <summary> /// <summary>
/// Default <see cref="ITwitterEvents"/> implementation. /// Default <see cref="ITwitterEvents"/> implementation.
/// </summary> /// </summary>
public class TwitterEvents : ITwitterEvents public class TwitterEvents : RemoteAuthenticationEvents, ITwitterEvents
{ {
/// <summary> /// <summary>
/// Gets or sets the function that is invoked when the Authenticated method is invoked. /// Gets or sets the function that is invoked when the Authenticated method is invoked.
/// </summary> /// </summary>
public Func<TwitterCreatingTicketContext, Task> OnCreatingTicket { get; set; } = context => Task.FromResult(0); 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> /// <summary>
/// Gets or sets the delegate that is invoked when the ApplyRedirect method is invoked. /// Gets or sets the delegate that is invoked when the ApplyRedirect method is invoked.
/// </summary> /// </summary>
@ -37,13 +32,6 @@ namespace Microsoft.AspNet.Authentication.Twitter
/// <returns>A <see cref="Task"/> representing the completed operation.</returns> /// <returns>A <see cref="Task"/> representing the completed operation.</returns>
public virtual Task CreatingTicket(TwitterCreatingTicketContext context) => OnCreatingTicket(context); 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> /// <summary>
/// Called when a Challenge causes a redirect to authorize endpoint in the Twitter middleware /// Called when a Challenge causes a redirect to authorize endpoint in the Twitter middleware
/// </summary> /// </summary>

View File

@ -9,7 +9,7 @@ namespace Microsoft.AspNet.Authentication.Twitter
/// <summary> /// <summary>
/// The Context passed when a Challenge causes a redirect to authorize endpoint in the Twitter middleware. /// The Context passed when a Challenge causes a redirect to authorize endpoint in the Twitter middleware.
/// </summary> /// </summary>
public class TwitterRedirectToAuthorizationEndpointContext : BaseContext<TwitterOptions> public class TwitterRedirectToAuthorizationEndpointContext : BaseTwitterContext
{ {
/// <summary> /// <summary>
/// Creates a new context object. /// Creates a new context object.

View File

@ -19,7 +19,7 @@ using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNet.Authentication.Twitter 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 static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private const string StateCookie = "__TwitterState"; private const string StateCookie = "__TwitterState";
@ -34,89 +34,70 @@ namespace Microsoft.AspNet.Authentication.Twitter
_httpClient = httpClient; _httpClient = httpClient;
} }
public override async Task<bool> InvokeAsync() protected override async Task<AuthenticateResult> HandleRemoteAuthenticateAsync()
{
if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path)
{
return await InvokeReturnPathAsync();
}
return false;
}
protected override async Task<AuthenticationTicket> HandleAuthenticateAsync()
{ {
AuthenticationProperties properties = null; 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; return AuthenticateResult.Failed("Invalid state cookie.");
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);
} }
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 AuthenticateResult.Failed("Missing oauth_token");
return new AuthenticationTicket(properties, Options.AuthenticationScheme);
} }
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) 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), Principal = new ClaimsPrincipal(identity),
Properties = properties Properties = properties
@ -145,83 +126,23 @@ namespace Microsoft.AspNet.Authentication.Twitter
properties.RedirectUri = CurrentUri; properties.RedirectUri = CurrentUri;
} }
// If CallbackConfirmed is false, this will throw
var requestToken = await ObtainRequestTokenAsync(Options.ConsumerKey, Options.ConsumerSecret, BuildRedirectUri(Options.CallbackPath), properties); 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; HttpOnly = true,
Secure = Request.IsHttps
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
}; };
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) var redirectContext = new TwitterRedirectToAuthorizationEndpointContext(
{ Context, Options,
await Context.Authentication.SignInAsync(context.SignInScheme, context.Principal, context.Properties); properties, twitterAuthenticationEndpoint);
} await Options.Events.RedirectToAuthorizationEndpoint(redirectContext);
return true;
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();
} }
private async Task<RequestToken> ObtainRequestTokenAsync(string consumerKey, string consumerSecret, string callBackUri, AuthenticationProperties properties) 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(); string responseText = await response.Content.ReadAsStringAsync();
var responseParameters = new FormCollection(FormReader.ReadForm(responseText)); 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) private async Task<AccessToken> ObtainAccessTokenAsync(string consumerKey, string consumerSecret, RequestToken token, string verifier)

View File

@ -78,6 +78,10 @@ namespace Microsoft.AspNet.Authentication.Twitter
{ {
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.ConsumerKey))); 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) if (Options.Events == null)
{ {

View File

@ -10,7 +10,7 @@ namespace Microsoft.AspNet.Authentication.Twitter
/// <summary> /// <summary>
/// Options for the Twitter authentication middleware. /// Options for the Twitter authentication middleware.
/// </summary> /// </summary>
public class TwitterOptions : AuthenticationOptions public class TwitterOptions : RemoteAuthenticationOptions
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TwitterOptions"/> class. /// Initializes a new instance of the <see cref="TwitterOptions"/> class.
@ -21,6 +21,7 @@ namespace Microsoft.AspNet.Authentication.Twitter
DisplayName = AuthenticationScheme; DisplayName = AuthenticationScheme;
CallbackPath = new PathString("/signin-twitter"); CallbackPath = new PathString("/signin-twitter");
BackchannelTimeout = TimeSpan.FromSeconds(60); BackchannelTimeout = TimeSpan.FromSeconds(60);
Events = new TwitterEvents();
} }
/// <summary> /// <summary>
@ -35,45 +36,6 @@ namespace Microsoft.AspNet.Authentication.Twitter
/// <value>The consumer secret used to sign requests to Twitter.</value> /// <value>The consumer secret used to sign requests to Twitter.</value>
public string ConsumerSecret { get; set; } 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> /// <summary>
/// Gets or sets the type used to secure data handled by the middleware. /// Gets or sets the type used to secure data handled by the middleware.
/// </summary> /// </summary>
@ -82,7 +44,11 @@ namespace Microsoft.AspNet.Authentication.Twitter
/// <summary> /// <summary>
/// Gets or sets the <see cref="ITwitterEvents"/> used to handle authentication events. /// Gets or sets the <see cref="ITwitterEvents"/> used to handle authentication events.
/// </summary> /// </summary>
public ITwitterEvents Events { get; set; } public new ITwitterEvents Events
{
get { return (ITwitterEvents)base.Events; }
set { base.Events = value; }
}
/// <summary> /// <summary>
/// Defines whether access tokens should be stored in the /// Defines whether access tokens should be stored in the

View File

@ -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) };
}
}
}

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication;
using Microsoft.AspNet.Http.Features.Authentication; using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.Extensions.Internal; using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -17,7 +18,7 @@ namespace Microsoft.AspNet.Authentication
/// <typeparam name="TOptions">Specifies which type for of AuthenticationOptions property</typeparam> /// <typeparam name="TOptions">Specifies which type for of AuthenticationOptions property</typeparam>
public abstract class AuthenticationHandler<TOptions> : IAuthenticationHandler where TOptions : AuthenticationOptions public abstract class AuthenticationHandler<TOptions> : IAuthenticationHandler where TOptions : AuthenticationOptions
{ {
private Task<AuthenticationTicket> _authenticateTask; private Task<AuthenticateResult> _authenticateTask;
private bool _finishCalled; private bool _finishCalled;
protected bool SignInAccepted { get; set; } protected bool SignInAccepted { get; set; }
@ -96,10 +97,10 @@ namespace Microsoft.AspNet.Authentication
Response.OnStarting(OnStartingCallback, this); Response.OnStarting(OnStartingCallback, this);
// Automatic authentication is the empty scheme if (ShouldHandleScheme(AuthenticationManager.AutomaticScheme, Options.AutomaticAuthenticate))
if (ShouldHandleScheme(string.Empty))
{ {
var ticket = await HandleAuthenticateOnceAsync(); var result = await HandleAuthenticateOnceAsync();
var ticket = result?.Ticket;
if (ticket?.Principal != null) if (ticket?.Principal != null)
{ {
Context.User = SecurityHelper.MergeUserPrincipal(Context.User, ticket.Principal); Context.User = SecurityHelper.MergeUserPrincipal(Context.User, ticket.Principal);
@ -139,7 +140,7 @@ namespace Microsoft.AspNet.Authentication
private async Task HandleAutomaticChallengeIfNeeded() private async Task HandleAutomaticChallengeIfNeeded()
{ {
if (!ChallengeCalled && Options.AutomaticAuthentication && Response.StatusCode == 401) if (!ChallengeCalled && Options.AutomaticChallenge && Response.StatusCode == 401)
{ {
await HandleUnauthorizedAsync(new ChallengeContext(Options.AuthenticationScheme)); 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 /// <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 /// cause the common code to begin the async completion journey without calling the rest of the middleware
/// pipeline.</returns> /// pipeline.</returns>
public virtual Task<bool> InvokeAsync() public virtual Task<bool> HandleRequestAsync()
{ {
return Task.FromResult(false); 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) || 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) 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. // Calling Authenticate more than once should always return the original value.
var ticket = await HandleAuthenticateOnceAsync(); var result = await HandleAuthenticateOnceAsync();
if (ticket?.Principal != null)
if (result?.Error != null)
{ {
context.Authenticated(ticket.Principal, ticket.Properties.Items, Options.Description.Items); context.Failed(result.Error);
} }
else 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); await PriorHandler.AuthenticateAsync(context);
} }
} }
protected Task<AuthenticationTicket> HandleAuthenticateOnceAsync() protected Task<AuthenticateResult> HandleAuthenticateOnceAsync()
{ {
if (_authenticateTask == null) if (_authenticateTask == null)
{ {
@ -221,18 +233,17 @@ namespace Microsoft.AspNet.Authentication
return _authenticateTask; return _authenticateTask;
} }
protected abstract Task<AuthenticationTicket> HandleAuthenticateAsync(); protected abstract Task<AuthenticateResult> HandleAuthenticateAsync();
public async Task SignInAsync(SignInContext context) public async Task SignInAsync(SignInContext context)
{ {
if (ShouldHandleScheme(context.AuthenticationScheme)) if (ShouldHandleScheme(context.AuthenticationScheme, handleAutomatic: false))
{ {
SignInAccepted = true; SignInAccepted = true;
await HandleSignInAsync(context); await HandleSignInAsync(context);
context.Accept(); context.Accept();
} }
else if (PriorHandler != null)
if (PriorHandler != null)
{ {
await PriorHandler.SignInAsync(context); await PriorHandler.SignInAsync(context);
} }
@ -245,14 +256,13 @@ namespace Microsoft.AspNet.Authentication
public async Task SignOutAsync(SignOutContext context) public async Task SignOutAsync(SignOutContext context)
{ {
if (ShouldHandleScheme(context.AuthenticationScheme)) if (ShouldHandleScheme(context.AuthenticationScheme, handleAutomatic: false))
{ {
SignOutAccepted = true; SignOutAccepted = true;
await HandleSignOutAsync(context); await HandleSignOutAsync(context);
context.Accept(); context.Accept();
} }
else if (PriorHandler != null)
if (PriorHandler != null)
{ {
await PriorHandler.SignOutAsync(context); await PriorHandler.SignOutAsync(context);
} }
@ -263,10 +273,6 @@ namespace Microsoft.AspNet.Authentication
return Task.FromResult(0); 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) protected virtual Task<bool> HandleForbiddenAsync(ChallengeContext context)
{ {
Response.StatusCode = 403; Response.StatusCode = 403;
@ -288,24 +294,20 @@ namespace Microsoft.AspNet.Authentication
public async Task ChallengeAsync(ChallengeContext context) public async Task ChallengeAsync(ChallengeContext context)
{ {
bool handled = false;
ChallengeCalled = true; ChallengeCalled = true;
if (ShouldHandleScheme(context.AuthenticationScheme)) var handled = false;
if (ShouldHandleScheme(context.AuthenticationScheme, Options.AutomaticChallenge))
{ {
switch (context.Behavior) switch (context.Behavior)
{ {
case ChallengeBehavior.Automatic: case ChallengeBehavior.Automatic:
// If there is a principal already, invoke the forbidden code path // If there is a principal already, invoke the forbidden code path
var ticket = await HandleAuthenticateOnceAsync(); var result = await HandleAuthenticateOnceAsync();
if (ticket?.Principal != null) if (result?.Ticket?.Principal != null)
{ {
handled = await HandleForbiddenAsync(context); goto case ChallengeBehavior.Forbidden;
} }
else goto case ChallengeBehavior.Unauthorized;
{
handled = await HandleUnauthorizedAsync(context);
}
break;
case ChallengeBehavior.Unauthorized: case ChallengeBehavior.Unauthorized:
handled = await HandleUnauthorizedAsync(context); handled = await HandleUnauthorizedAsync(context);
break; break;

View File

@ -67,7 +67,7 @@ namespace Microsoft.AspNet.Authentication
await handler.InitializeAsync(Options, context, Logger, UrlEncoder); await handler.InitializeAsync(Options, context, Logger, UrlEncoder);
try try
{ {
if (!await handler.InvokeAsync()) if (!await handler.HandleRequestAsync())
{ {
await _next(context); await _next(context);
} }
@ -84,7 +84,6 @@ namespace Microsoft.AspNet.Authentication
} }
throw; throw;
} }
await handler.TeardownAsync();
} }
protected abstract AuthenticationHandler<TOptions> CreateHandler(); protected abstract AuthenticationHandler<TOptions> CreateHandler();

View File

@ -27,11 +27,16 @@ namespace Microsoft.AspNet.Authentication
} }
/// <summary> /// <summary>
/// If true the authentication middleware alter the request user coming in and /// If true the authentication middleware alter the request user coming in. If false the authentication middleware will only provide
/// alter 401 Unauthorized responses going out. If false the authentication middleware will only provide /// identity when explicitly indicated by the AuthenticationScheme.
/// identity and alter responses when explicitly indicated by the AuthenticationScheme.
/// </summary> /// </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> /// <summary>
/// Gets or sets the issuer that should be used for any claims that are created /// Gets or sets the issuer that should be used for any claims that are created

View File

@ -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; }
}
}
}

View File

@ -5,9 +5,9 @@ using Microsoft.AspNet.Http;
namespace Microsoft.AspNet.Authentication 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)
{ {
} }

View File

@ -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; }
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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 System.Security.Claims;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication; using Microsoft.AspNet.Http.Authentication;
@ -11,13 +10,12 @@ namespace Microsoft.AspNet.Authentication
/// <summary> /// <summary>
/// Provides context information to middleware providers. /// Provides context information to middleware providers.
/// </summary> /// </summary>
public class SigningInContext : BaseContext public class TicketReceivedContext : BaseControlContext
{ {
public SigningInContext( public TicketReceivedContext(HttpContext context, RemoteAuthenticationOptions options, AuthenticationTicket ticket)
HttpContext context,
AuthenticationTicket ticket)
: base(context) : base(context)
{ {
Options = options;
if (ticket != null) if (ticket != null)
{ {
Principal = ticket.Principal; Principal = ticket.Principal;
@ -27,17 +25,8 @@ namespace Microsoft.AspNet.Authentication
public ClaimsPrincipal Principal { get; set; } public ClaimsPrincipal Principal { get; set; }
public AuthenticationProperties Properties { get; set; } public AuthenticationProperties Properties { get; set; }
public RemoteAuthenticationOptions Options { get; set; }
public bool IsRequestCompleted { get; private set; } public string ReturnUri { get; set; }
public void RequestCompleted()
{
IsRequestCompleted = true;
}
public string SignInScheme { get; set; }
[SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "By design")]
public string RedirectUri { get; set; }
} }
} }

View File

@ -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();
}
}
}

View File

@ -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();
}
}

View File

@ -18,7 +18,15 @@
"Microsoft.Extensions.WebEncoders": "1.0.0-*" "Microsoft.Extensions.WebEncoders": "1.0.0-*"
}, },
"frameworks": { "frameworks": {
"dnx451": { }, "dnx451": {
"dnxcore50": { } "frameworkAssemblies": {
"System.Net.Http": ""
}
},
"dnxcore50": {
"dependencies": {
"System.Net.Http": "4.0.1-beta-*"
}
}
} }
} }

View File

@ -9,16 +9,16 @@ namespace Microsoft.AspNet.Authorization
{ {
public class AuthorizationPolicy public class AuthorizationPolicy
{ {
public AuthorizationPolicy(IEnumerable<IAuthorizationRequirement> requirements, IEnumerable<string> activeAuthenticationSchemes) public AuthorizationPolicy(IEnumerable<IAuthorizationRequirement> requirements, IEnumerable<string> authenticationSchemes)
{ {
if (requirements == null) if (requirements == null)
{ {
throw new ArgumentNullException(nameof(requirements)); 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) if (requirements.Count() == 0)
@ -26,11 +26,11 @@ namespace Microsoft.AspNet.Authorization
throw new InvalidOperationException(Resources.Exception_AuthorizationPolicyEmpty); throw new InvalidOperationException(Resources.Exception_AuthorizationPolicyEmpty);
} }
Requirements = new List<IAuthorizationRequirement>(requirements).AsReadOnly(); 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<IAuthorizationRequirement> Requirements { get; }
public IReadOnlyList<string> ActiveAuthenticationSchemes { get; } public IReadOnlyList<string> AuthenticationSchemes { get; }
public static AuthorizationPolicy Combine(params AuthorizationPolicy[] policies) public static AuthorizationPolicy Combine(params AuthorizationPolicy[] policies)
{ {
@ -96,7 +96,7 @@ namespace Microsoft.AspNet.Authorization
{ {
foreach (var authType in authTypesSplit) foreach (var authType in authTypesSplit)
{ {
policyBuilder.ActiveAuthenticationSchemes.Add(authType); policyBuilder.AuthenticationSchemes.Add(authType);
} }
} }
if (useDefaultPolicy) if (useDefaultPolicy)

View File

@ -9,9 +9,9 @@ namespace Microsoft.AspNet.Authorization
{ {
public class AuthorizationPolicyBuilder public class AuthorizationPolicyBuilder
{ {
public AuthorizationPolicyBuilder(params string[] activeAuthenticationSchemes) public AuthorizationPolicyBuilder(params string[] authenticationSchemes)
{ {
AddAuthenticationSchemes(activeAuthenticationSchemes); AddAuthenticationSchemes(authenticationSchemes);
} }
public AuthorizationPolicyBuilder(AuthorizationPolicy policy) public AuthorizationPolicyBuilder(AuthorizationPolicy policy)
@ -20,13 +20,13 @@ namespace Microsoft.AspNet.Authorization
} }
public IList<IAuthorizationRequirement> Requirements { get; set; } = new List<IAuthorizationRequirement>(); 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; return this;
} }
@ -47,7 +47,7 @@ namespace Microsoft.AspNet.Authorization
throw new ArgumentNullException(nameof(policy)); throw new ArgumentNullException(nameof(policy));
} }
AddAuthenticationSchemes(policy.ActiveAuthenticationSchemes.ToArray()); AddAuthenticationSchemes(policy.AuthenticationSchemes.ToArray());
AddRequirements(policy.Requirements.ToArray()); AddRequirements(policy.Requirements.ToArray());
return this; return this;
} }
@ -135,7 +135,7 @@ namespace Microsoft.AspNet.Authorization
public AuthorizationPolicy Build() public AuthorizationPolicy Build()
{ {
return new AuthorizationPolicy(Requirements, ActiveAuthenticationSchemes.Distinct()); return new AuthorizationPolicy(Requirements, AuthenticationSchemes.Distinct());
} }
} }
} }

View File

@ -5,6 +5,7 @@ using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication;
using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Http.Features.Authentication; using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Http.Internal;
@ -19,10 +20,10 @@ namespace Microsoft.AspNet.Authentication
public async Task ShouldHandleSchemeAreDeterminedOnlyByMatchingAuthenticationScheme() public async Task ShouldHandleSchemeAreDeterminedOnlyByMatchingAuthenticationScheme()
{ {
var handler = await TestHandler.Create("Alpha"); var handler = await TestHandler.Create("Alpha");
var passiveNoMatch = handler.ShouldHandleScheme("Beta"); var passiveNoMatch = handler.ShouldHandleScheme("Beta", handleAutomatic: false);
handler = await TestHandler.Create("Alpha"); handler = await TestHandler.Create("Alpha");
var passiveWithMatch = handler.ShouldHandleScheme("Alpha"); var passiveWithMatch = handler.ShouldHandleScheme("Alpha", handleAutomatic: false);
Assert.False(passiveNoMatch); Assert.False(passiveNoMatch);
Assert.True(passiveWithMatch); Assert.True(passiveWithMatch);
@ -32,47 +33,37 @@ namespace Microsoft.AspNet.Authentication
public async Task AutomaticHandlerInAutomaticModeHandlesEmptyChallenges() public async Task AutomaticHandlerInAutomaticModeHandlesEmptyChallenges()
{ {
var handler = await TestAutoHandler.Create("ignored", true); var handler = await TestAutoHandler.Create("ignored", true);
Assert.True(handler.ShouldHandleScheme("")); Assert.True(handler.ShouldHandleScheme(AuthenticationManager.AutomaticScheme, handleAutomatic: true));
} }
[Fact] [Theory]
public async Task AutomaticHandlerHandlesNullScheme() [InlineData(null)]
[InlineData("")]
[InlineData(" ")]
[InlineData("notmatched")]
public async Task AutomaticHandlerDoesNotHandleSchemes(string scheme)
{ {
var handler = await TestAutoHandler.Create("ignored", true); var handler = await TestAutoHandler.Create("ignored", true);
Assert.True(handler.ShouldHandleScheme(null)); Assert.False(handler.ShouldHandleScheme(scheme, handleAutomatic: true));
}
[Fact]
public async Task AutomaticHandlerIgnoresWhitespaceScheme()
{
var handler = await TestAutoHandler.Create("ignored", true);
Assert.False(handler.ShouldHandleScheme(" "));
} }
[Fact] [Fact]
public async Task AutomaticHandlerShouldHandleSchemeWhenSchemeMatches() public async Task AutomaticHandlerShouldHandleSchemeWhenSchemeMatches()
{ {
var handler = await TestAutoHandler.Create("Alpha", true); var handler = await TestAutoHandler.Create("Alpha", true);
Assert.True(handler.ShouldHandleScheme("Alpha")); Assert.True(handler.ShouldHandleScheme("Alpha", handleAutomatic: true));
}
[Fact]
public async Task AutomaticHandlerShouldNotHandleChallengeWhenSchemeDoesNotMatches()
{
var handler = await TestAutoHandler.Create("Dog", true);
Assert.False(handler.ShouldHandleScheme("Alpha"));
} }
[Fact] [Fact]
public async Task AutomaticHandlerShouldNotHandleChallengeWhenSchemesNotEmpty() public async Task AutomaticHandlerShouldNotHandleChallengeWhenSchemesNotEmpty()
{ {
var handler = await TestAutoHandler.Create(null, true); var handler = await TestAutoHandler.Create(null, true);
Assert.False(handler.ShouldHandleScheme("Alpha")); Assert.False(handler.ShouldHandleScheme("Alpha", handleAutomatic: true));
} }
[Theory] [Theory]
[InlineData("Alpha")] [InlineData("Alpha")]
[InlineData("")] [InlineData("Automatic")]
public async Task AuthHandlerAuthenticateCachesTicket(string scheme) public async Task AuthHandlerAuthenticateCachesTicket(string scheme)
{ {
var handler = await CountHandler.Create(scheme); var handler = await CountHandler.Create(scheme);
@ -100,14 +91,14 @@ namespace Microsoft.AspNet.Authentication
new LoggerFactory().CreateLogger("CountHandler"), new LoggerFactory().CreateLogger("CountHandler"),
Extensions.WebEncoders.UrlEncoder.Default); Extensions.WebEncoders.UrlEncoder.Default);
handler.Options.AuthenticationScheme = scheme; handler.Options.AuthenticationScheme = scheme;
handler.Options.AutomaticAuthentication = true; handler.Options.AutomaticAuthenticate = true;
return handler; return handler;
} }
protected override Task<AuthenticationTicket> HandleAuthenticateAsync() protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{ {
AuthCount++; 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; 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() public TestAutoOptions()
{ {
AutomaticAuthentication = true; AutomaticAuthenticate = true;
} }
} }
@ -159,13 +150,13 @@ namespace Microsoft.AspNet.Authentication
new LoggerFactory().CreateLogger("TestAutoHandler"), new LoggerFactory().CreateLogger("TestAutoHandler"),
Extensions.WebEncoders.UrlEncoder.Default); Extensions.WebEncoders.UrlEncoder.Default);
handler.Options.AuthenticationScheme = scheme; handler.Options.AuthenticationScheme = scheme;
handler.Options.AutomaticAuthentication = auto; handler.Options.AutomaticAuthenticate = auto;
return handler; 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")));
} }
} }

View File

@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
var server = CreateServer(options => var server = CreateServer(options =>
{ {
options.LoginPath = new PathString("/login"); options.LoginPath = new PathString("/login");
options.AutomaticAuthentication = auto; options.AutomaticChallenge = auto;
}); });
var transaction = await SendAsync(server, "http://example.com/protected"); var transaction = await SendAsync(server, "http://example.com/protected");
@ -60,7 +60,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
[Fact] [Fact]
public async Task ProtectedCustomRequestShouldRedirectToCustomRedirectUri() 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"); var transaction = await SendAsync(server, "http://example.com/protected/CustomRedirect");
@ -573,7 +573,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
var clock = new TestClock(); var clock = new TestClock();
var server = CreateServer(options => var server = CreateServer(options =>
{ {
options.AutomaticAuthentication = automatic; options.AutomaticAuthenticate = automatic;
options.SystemClock = clock; options.SystemClock = clock;
}, },
SignInAsAlice); SignInAsAlice);
@ -596,7 +596,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
var clock = new TestClock(); var clock = new TestClock();
var server = CreateServer(options => var server = CreateServer(options =>
{ {
options.AutomaticAuthentication = automatic; options.AutomaticAuthenticate = automatic;
options.SystemClock = clock; options.SystemClock = clock;
}, },
SignInAsAlice); SignInAsAlice);
@ -617,7 +617,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
var clock = new TestClock(); var clock = new TestClock();
var server = CreateServer(options => var server = CreateServer(options =>
{ {
options.AutomaticAuthentication = automatic; options.AutomaticAuthenticate = automatic;
options.SystemClock = clock; options.SystemClock = clock;
}, },
SignInAsAlice); SignInAsAlice);
@ -1002,10 +1002,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
} }
}); });
}, },
services => services => services.AddAuthentication());
{
services.AddAuthentication();
});
server.BaseAddress = baseAddress; server.BaseAddress = baseAddress;
return server; return server;
} }

View File

@ -18,6 +18,8 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.WebEncoders; using Microsoft.Extensions.WebEncoders;
using Newtonsoft.Json; using Newtonsoft.Json;
using Xunit; using Xunit;
using System.Diagnostics;
using Microsoft.AspNet.Authentication.Cookies;
namespace Microsoft.AspNet.Authentication.Facebook namespace Microsoft.AspNet.Authentication.Facebook
{ {
@ -45,7 +47,7 @@ namespace Microsoft.AspNet.Authentication.Facebook
app.UseCookieAuthentication(options => app.UseCookieAuthentication(options =>
{ {
options.AuthenticationScheme = "External"; options.AuthenticationScheme = "External";
options.AutomaticAuthentication = true; options.AutomaticAuthenticate = true;
}); });
}, },
services => services =>
@ -158,11 +160,12 @@ namespace Microsoft.AspNet.Authentication.Facebook
public async Task CustomUserInfoEndpointHasValidGraphQuery() public async Task CustomUserInfoEndpointHasValidGraphQuery()
{ {
var customUserInfoEndpoint = "https://graph.facebook.com/me?fields=email,timezone,picture"; 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 stateFormat = new PropertiesDataFormat(new EphemeralDataProtectionProvider().CreateProtector("FacebookTest"));
var server = CreateServer( var server = CreateServer(
app => app =>
{ {
app.UseCookieAuthentication();
app.UseFacebookAuthentication(options => app.UseFacebookAuthentication(options =>
{ {
options.AppId = "Test App Id"; options.AppId = "Test App Id";
@ -200,11 +203,10 @@ namespace Microsoft.AspNet.Authentication.Facebook
} }
}; };
}); });
app.UseCookieAuthentication();
}, },
services => services =>
{ {
services.AddAuthentication(); services.AddAuthentication(options => options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme);
}, handler: null); }, handler: null);
var properties = new AuthenticationProperties(); var properties = new AuthenticationProperties();

View File

@ -13,6 +13,7 @@ using Microsoft.AspNet.Builder;
using Microsoft.AspNet.DataProtection; using Microsoft.AspNet.DataProtection;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication; using Microsoft.AspNet.Http.Authentication;
using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.AspNet.TestHost; using Microsoft.AspNet.TestHost;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.WebEncoders; using Microsoft.Extensions.WebEncoders;
@ -88,7 +89,7 @@ namespace Microsoft.AspNet.Authentication.Google
{ {
options.ClientId = "Test Id"; options.ClientId = "Test Id";
options.ClientSecret = "Test Secret"; options.ClientSecret = "Test Secret";
options.AutomaticAuthentication = true; options.AutomaticChallenge = true;
}); });
var transaction = await server.SendAsync("https://example.com/401"); var transaction = await server.SendAsync("https://example.com/401");
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode); Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
@ -119,7 +120,7 @@ namespace Microsoft.AspNet.Authentication.Google
{ {
options.ClientId = "Test Id"; options.ClientId = "Test Id";
options.ClientSecret = "Test Secret"; options.ClientSecret = "Test Secret";
options.AutomaticAuthentication = true; options.AutomaticChallenge = true;
}); });
var transaction = await server.SendAsync("https://example.com/401"); var transaction = await server.SendAsync("https://example.com/401");
Assert.Contains(".AspNet.Correlation.Google=", transaction.SetCookie.Single()); Assert.Contains(".AspNet.Correlation.Google=", transaction.SetCookie.Single());
@ -146,7 +147,7 @@ namespace Microsoft.AspNet.Authentication.Google
{ {
options.ClientId = "Test Id"; options.ClientId = "Test Id";
options.ClientSecret = "Test Secret"; options.ClientSecret = "Test Secret";
options.AutomaticAuthentication = true; options.AutomaticChallenge = true;
}); });
var transaction = await server.SendAsync("https://example.com/401"); var transaction = await server.SendAsync("https://example.com/401");
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode); Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
@ -161,7 +162,7 @@ namespace Microsoft.AspNet.Authentication.Google
{ {
options.ClientId = "Test Id"; options.ClientId = "Test Id";
options.ClientSecret = "Test Secret"; options.ClientSecret = "Test Secret";
options.AutomaticAuthentication = true; options.AutomaticChallenge = true;
}, },
context => context =>
{ {
@ -212,6 +213,29 @@ namespace Microsoft.AspNet.Authentication.Google
Assert.Contains("custom=test", query); 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] [Fact]
public async Task ReplyPathWithoutStateQueryStringWillBeRejected() public async Task ReplyPathWithoutStateQueryStringWillBeRejected()
{ {
@ -224,6 +248,40 @@ namespace Microsoft.AspNet.Authentication.Google
Assert.Equal(HttpStatusCode.InternalServerError, transaction.Response.StatusCode); 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] [Theory]
[InlineData(null)] [InlineData(null)]
[InlineData("CustomIssuer")] [InlineData("CustomIssuer")]
@ -305,8 +363,11 @@ namespace Microsoft.AspNet.Authentication.Google
Assert.Equal("yup", transaction.FindClaimValue("xform")); Assert.Equal("yup", transaction.FindClaimValue("xform"));
} }
[Fact] // REVIEW: Fix this once we revisit error handling to not blow up
public async Task ReplyPathWillRejectIfCodeIsInvalid() [Theory]
[InlineData(true)]
[InlineData(false)]
public async Task ReplyPathWillThrowIfCodeIsInvalid(bool redirect)
{ {
var stateFormat = new PropertiesDataFormat(new EphemeralDataProtectionProvider().CreateProtector("GoogleTest")); var stateFormat = new PropertiesDataFormat(new EphemeralDataProtectionProvider().CreateProtector("GoogleTest"));
var server = CreateServer(options => var server = CreateServer(options =>
@ -321,22 +382,50 @@ namespace Microsoft.AspNet.Authentication.Google
return new HttpResponseMessage(HttpStatusCode.BadRequest); 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 properties = new AuthenticationProperties();
var correlationKey = ".AspNet.Correlation.Google"; var correlationKey = ".AspNet.Correlation.Google";
var correlationValue = "TestCorrelationId"; var correlationValue = "TestCorrelationId";
properties.Items.Add(correlationKey, correlationValue); properties.Items.Add(correlationKey, correlationValue);
properties.RedirectUri = "/me"; properties.RedirectUri = "/me";
var state = stateFormat.Protect(properties); 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), "https://example.com/signin-google?code=TestCode&state=" + UrlEncoder.Default.UrlEncode(state),
correlationKey + "=" + correlationValue); correlationKey + "=" + correlationValue));
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
Assert.Contains("error=access_denied", transaction.Response.Headers.Location.ToString()); //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] [Theory]
public async Task ReplyPathWillRejectIfAccessTokenIsMissing() [InlineData(true)]
[InlineData(false)]
public async Task ReplyPathWillRejectIfAccessTokenIsMissing(bool redirect)
{ {
var stateFormat = new PropertiesDataFormat(new EphemeralDataProtectionProvider().CreateProtector("GoogleTest")); var stateFormat = new PropertiesDataFormat(new EphemeralDataProtectionProvider().CreateProtector("GoogleTest"));
var server = CreateServer(options => var server = CreateServer(options =>
@ -351,6 +440,18 @@ namespace Microsoft.AspNet.Authentication.Google
return ReturnJsonResponse(new object()); 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 properties = new AuthenticationProperties();
var correlationKey = ".AspNet.Correlation.Google"; var correlationKey = ".AspNet.Correlation.Google";
@ -361,8 +462,16 @@ namespace Microsoft.AspNet.Authentication.Google
var transaction = await server.SendAsync( var transaction = await server.SendAsync(
"https://example.com/signin-google?code=TestCode&state=" + UrlEncoder.Default.UrlEncode(state), "https://example.com/signin-google?code=TestCode&state=" + UrlEncoder.Default.UrlEncode(state),
correlationKey + "=" + correlationValue); correlationKey + "=" + correlationValue);
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode); if (redirect)
Assert.Contains("error=access_denied", transaction.Response.Headers.Location.ToString()); {
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] [Fact]
@ -420,7 +529,7 @@ namespace Microsoft.AspNet.Authentication.Google
{ {
var refreshToken = context.RefreshToken; var refreshToken = context.RefreshToken;
context.Principal.AddIdentity(new ClaimsIdentity(new Claim[] { new Claim("RefreshToken", refreshToken, ClaimValueTypes.String, "Google") }, "Google")); 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()); 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) private static HttpResponseMessage ReturnJsonResponse(object content)
{ {
@ -545,7 +698,7 @@ namespace Microsoft.AspNet.Authentication.Google
app.UseCookieAuthentication(options => app.UseCookieAuthentication(options =>
{ {
options.AuthenticationScheme = TestExtensions.CookieAuthenticationScheme; options.AuthenticationScheme = TestExtensions.CookieAuthenticationScheme;
options.AutomaticAuthentication = true; options.AutomaticAuthenticate = true;
}); });
app.UseGoogleAuthentication(configureOptions); app.UseGoogleAuthentication(configureOptions);
app.UseClaimsTransformation(p => app.UseClaimsTransformation(p =>

View File

@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
{ {
var server = CreateServer(options => var server = CreateServer(options =>
{ {
options.AutomaticAuthentication = true; options.AutomaticAuthenticate = true;
options.Authority = "https://login.windows.net/tushartest.onmicrosoft.com"; options.Authority = "https://login.windows.net/tushartest.onmicrosoft.com";
options.Audience = "https://TusharTest.onmicrosoft.com/TodoListService-ManualJwt"; options.Audience = "https://TusharTest.onmicrosoft.com/TodoListService-ManualJwt";
@ -44,7 +44,7 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
{ {
var server = CreateServer(options => var server = CreateServer(options =>
{ {
options.AutomaticAuthentication = true; options.AutomaticAuthenticate = true;
}); });
var transaction = await server.SendAsync("https://example.com/signIn"); var transaction = await server.SendAsync("https://example.com/signIn");
Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode); Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode);
@ -55,7 +55,7 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
{ {
var server = CreateServer(options => var server = CreateServer(options =>
{ {
options.AutomaticAuthentication = true; options.AutomaticAuthenticate = true;
}); });
var transaction = await server.SendAsync("https://example.com/signOut"); var transaction = await server.SendAsync("https://example.com/signOut");
Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode); Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode);
@ -67,7 +67,7 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
{ {
var server = CreateServer(options => var server = CreateServer(options =>
{ {
options.AutomaticAuthentication = true; options.AutomaticAuthenticate = true;
options.Events = new JwtBearerEvents() options.Events = new JwtBearerEvents()
{ {
@ -117,7 +117,7 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
{ {
var server = CreateServer(options => var server = CreateServer(options =>
{ {
options.AutomaticAuthentication = true; options.AutomaticAuthenticate = true;
options.Events = new JwtBearerEvents() options.Events = new JwtBearerEvents()
{ {
@ -151,7 +151,7 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
{ {
var server = CreateServer(options => var server = CreateServer(options =>
{ {
options.AutomaticAuthentication = true; options.AutomaticAuthenticate = true;
options.Events = new JwtBearerEvents() options.Events = new JwtBearerEvents()
{ {
@ -188,7 +188,7 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
{ {
var server = CreateServer(options => var server = CreateServer(options =>
{ {
options.AutomaticAuthentication = true; options.AutomaticAuthenticate = true;
options.Events = new JwtBearerEvents() options.Events = new JwtBearerEvents()
{ {

View File

@ -182,7 +182,7 @@ namespace Microsoft.AspNet.Authentication.Tests.MicrosoftAccount
app.UseCookieAuthentication(options => app.UseCookieAuthentication(options =>
{ {
options.AuthenticationScheme = TestExtensions.CookieAuthenticationScheme; options.AuthenticationScheme = TestExtensions.CookieAuthenticationScheme;
options.AutomaticAuthentication = true; options.AutomaticAuthenticate = true;
}); });
app.UseMicrosoftAccountAuthentication(configureOptions); app.UseMicrosoftAccountAuthentication(configureOptions);

View File

@ -5,8 +5,6 @@ using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Authentication.OpenIdConnect; using Microsoft.AspNet.Authentication.OpenIdConnect;
using Microsoft.AspNet.Http.Authentication;
using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@ -17,16 +15,10 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
/// </summary> /// </summary>
public class OpenIdConnectHandlerForTestingAuthenticate : OpenIdConnectHandler public class OpenIdConnectHandlerForTestingAuthenticate : OpenIdConnectHandler
{ {
public OpenIdConnectHandlerForTestingAuthenticate() public OpenIdConnectHandlerForTestingAuthenticate() : base(null)
: base(null)
{ {
} }
protected override async Task<bool> HandleUnauthorizedAsync(ChallengeContext context)
{
return await base.HandleUnauthorizedAsync(context);
}
protected override Task<OpenIdConnectTokenEndpointResponse> RedeemAuthorizationCodeAsync(string authorizationCode, string redirectUri) protected override Task<OpenIdConnectTokenEndpointResponse> RedeemAuthorizationCodeAsync(string authorizationCode, string redirectUri)
{ {
var jsonResponse = new JObject(); var jsonResponse = new JObject();

View File

@ -98,6 +98,7 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
options.ConfigurationManager = TestUtilities.DefaultOpenIdConnectConfigurationManager; options.ConfigurationManager = TestUtilities.DefaultOpenIdConnectConfigurationManager;
options.ClientId = Guid.NewGuid().ToString(); options.ClientId = Guid.NewGuid().ToString();
options.StateDataFormat = new AuthenticationPropertiesFormaterKeyValue(); options.StateDataFormat = new AuthenticationPropertiesFormaterKeyValue();
options.SignInScheme = "Cookies";
options.Events = new OpenIdConnectEvents() options.Events = new OpenIdConnectEvents()
{ {
OnTokenResponseReceived = context => OnTokenResponseReceived = context =>

View File

@ -154,7 +154,7 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
var mockOpenIdConnectMessage = new Mock<OpenIdConnectMessage>(); var mockOpenIdConnectMessage = new Mock<OpenIdConnectMessage>();
mockOpenIdConnectMessage.Setup(m => m.CreateAuthenticationRequestUrl()).Returns(ExpectedAuthorizeRequest); mockOpenIdConnectMessage.Setup(m => m.CreateAuthenticationRequestUrl()).Returns(ExpectedAuthorizeRequest);
mockOpenIdConnectMessage.Setup(m => m.CreateLogoutRequestUrl()).Returns(ExpectedLogoutRequest); mockOpenIdConnectMessage.Setup(m => m.CreateLogoutRequestUrl()).Returns(ExpectedLogoutRequest);
options.AutomaticAuthentication = true; options.AutomaticChallenge = true;
options.Events = new OpenIdConnectEvents() options.Events = new OpenIdConnectEvents()
{ {
OnRedirectToAuthenticationEndpoint = (context) => OnRedirectToAuthenticationEndpoint = (context) =>
@ -191,7 +191,7 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
var server = CreateServer(options => var server = CreateServer(options =>
{ {
SetOptions(options, DefaultParameters(new string[] { OpenIdConnectParameterNames.State }), queryValues, stateDataFormat); SetOptions(options, DefaultParameters(new string[] { OpenIdConnectParameterNames.State }), queryValues, stateDataFormat);
options.AutomaticAuthentication = challenge.Equals(ChallengeWithOutContext); options.AutomaticChallenge = challenge.Equals(ChallengeWithOutContext);
options.Events = new OpenIdConnectEvents() options.Events = new OpenIdConnectEvents()
{ {
OnRedirectToAuthenticationEndpoint = context => OnRedirectToAuthenticationEndpoint = context =>
@ -306,7 +306,7 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
private static void DefaultChallengeOptions(OpenIdConnectOptions options) private static void DefaultChallengeOptions(OpenIdConnectOptions options)
{ {
options.AuthenticationScheme = "OpenIdConnectHandlerTest"; options.AuthenticationScheme = "OpenIdConnectHandlerTest";
options.AutomaticAuthentication = true; options.AutomaticChallenge = true;
options.ClientId = Guid.NewGuid().ToString(); options.ClientId = Guid.NewGuid().ToString();
options.ConfigurationManager = TestUtilities.DefaultOpenIdConnectConfigurationManager; options.ConfigurationManager = TestUtilities.DefaultOpenIdConnectConfigurationManager;
options.StateDataFormat = new AuthenticationPropertiesFormaterKeyValue(); options.StateDataFormat = new AuthenticationPropertiesFormaterKeyValue();

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. See License.txt in the project root for license information. // Copyright (c) .NET Foundation. All rights reserved. See License.txt in the project root for license information.
using System; using System;
using System.Linq;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Security.Claims; using System.Security.Claims;
@ -10,6 +11,7 @@ using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.TestHost; using Microsoft.AspNet.TestHost;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.WebEncoders;
using Xunit; using Xunit;
namespace Microsoft.AspNet.Authentication.Twitter namespace Microsoft.AspNet.Authentication.Twitter
@ -61,6 +63,22 @@ namespace Microsoft.AspNet.Authentication.Twitter
Assert.Contains("custom=test", query); 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] [Fact]
public async Task SignInThrows() public async Task SignInThrows()
{ {

View File

@ -34,9 +34,9 @@ namespace Microsoft.AspNet.Authroization.Test
var combined = AuthorizationPolicy.Combine(options, attributes); var combined = AuthorizationPolicy.Combine(options, attributes);
// Assert // Assert
Assert.Equal(2, combined.ActiveAuthenticationSchemes.Count()); Assert.Equal(2, combined.AuthenticationSchemes.Count());
Assert.True(combined.ActiveAuthenticationSchemes.Contains("dupe")); Assert.True(combined.AuthenticationSchemes.Contains("dupe"));
Assert.True(combined.ActiveAuthenticationSchemes.Contains("roles")); Assert.True(combined.AuthenticationSchemes.Contains("roles"));
Assert.Equal(4, combined.Requirements.Count()); Assert.Equal(4, combined.Requirements.Count());
Assert.True(combined.Requirements.Any(r => r is DenyAnonymousAuthorizationRequirement)); Assert.True(combined.Requirements.Any(r => r is DenyAnonymousAuthorizationRequirement));
Assert.Equal(2, combined.Requirements.OfType<ClaimsAuthorizationRequirement>().Count()); Assert.Equal(2, combined.Requirements.OfType<ClaimsAuthorizationRequirement>().Count());
@ -59,9 +59,9 @@ namespace Microsoft.AspNet.Authroization.Test
var combined = AuthorizationPolicy.Combine(options, attributes); var combined = AuthorizationPolicy.Combine(options, attributes);
// Assert // Assert
Assert.Equal(2, combined.ActiveAuthenticationSchemes.Count()); Assert.Equal(2, combined.AuthenticationSchemes.Count());
Assert.True(combined.ActiveAuthenticationSchemes.Contains("dupe")); Assert.True(combined.AuthenticationSchemes.Contains("dupe"));
Assert.True(combined.ActiveAuthenticationSchemes.Contains("default")); Assert.True(combined.AuthenticationSchemes.Contains("default"));
Assert.Equal(2, combined.Requirements.Count()); Assert.Equal(2, combined.Requirements.Count());
Assert.False(combined.Requirements.Any(r => r is DenyAnonymousAuthorizationRequirement)); Assert.False(combined.Requirements.Any(r => r is DenyAnonymousAuthorizationRequirement));
Assert.Equal(2, combined.Requirements.OfType<ClaimsAuthorizationRequirement>().Count()); Assert.Equal(2, combined.Requirements.OfType<ClaimsAuthorizationRequirement>().Count());