Auth API changes (Async, ChallengeBehavior)

This commit is contained in:
Hao Kung 2015-06-25 17:03:10 -07:00
parent 641a7fb82b
commit 5fe8037281
13 changed files with 139 additions and 169 deletions

View File

@ -1,44 +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.Framework.Internal;
namespace Microsoft.AspNet.Http.Authentication
{
/// <summary>
/// Acts as the return value from calls to the IAuthenticationManager's AuthenticeAsync methods.
/// </summary>
public class AuthenticationResult
{
/// <summary>
/// Create an instance of the result object
/// </summary>
/// <param name="identity">Assigned to Identity. May be null.</param>
/// <param name="properties">Assigned to Properties. Contains extra information carried along with the identity.</param>
/// <param name="description">Assigned to Description. Contains information describing the authentication provider.</param>
public AuthenticationResult(ClaimsPrincipal principal, [NotNull] AuthenticationProperties properties, [NotNull] AuthenticationDescription description)
{
Principal = principal;
Properties = properties;
Description = description;
}
/// <summary>
/// Contains the claims that were authenticated by the given AuthenticationScheme. If the authentication
/// scheme was not successful the Identity property will be null.
/// </summary>
public ClaimsPrincipal Principal { get; private set; }
/// <summary>
/// Contains extra values that were provided with the original SignIn call.
/// </summary>
public AuthenticationProperties Properties { get; private set; }
/// <summary>
/// Contains description properties for the middleware authentication type in general. Does not
/// vary per request.
/// </summary>
public AuthenticationDescription Description { get; private set; }
}
}

View File

@ -4,6 +4,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Http.Authentication namespace Microsoft.AspNet.Http.Authentication
{ {
@ -11,41 +13,61 @@ namespace Microsoft.AspNet.Http.Authentication
{ {
public abstract IEnumerable<AuthenticationDescription> GetAuthenticationSchemes(); public abstract IEnumerable<AuthenticationDescription> GetAuthenticationSchemes();
public abstract AuthenticationResult Authenticate(string authenticationScheme); public abstract Task AuthenticateAsync([NotNull] AuthenticateContext context);
public abstract Task<AuthenticationResult> AuthenticateAsync(string authenticationScheme); public virtual async Task<ClaimsPrincipal> AuthenticateAsync([NotNull] string authenticationScheme)
public virtual void Challenge()
{ {
Challenge(authenticationScheme: null, properties: null); var context = new AuthenticateContext(authenticationScheme);
await AuthenticateAsync(context);
return context.Principal;
} }
public virtual void Challenge(AuthenticationProperties properties) public virtual Task ChallengeAsync()
{ {
Challenge(authenticationScheme: null, properties: properties); return ChallengeAsync(properties: null);
} }
public virtual void Challenge(string authenticationScheme) public virtual Task ChallengeAsync(AuthenticationProperties properties)
{ {
Challenge(authenticationScheme: authenticationScheme, properties: null); return ChallengeAsync(authenticationScheme: string.Empty, properties: properties);
} }
public abstract void Challenge(string authenticationScheme, AuthenticationProperties properties); public virtual Task ChallengeAsync([NotNull] string authenticationScheme)
public void SignIn(string authenticationScheme, ClaimsPrincipal principal)
{ {
SignIn(authenticationScheme, principal, properties: null); return ChallengeAsync(authenticationScheme: authenticationScheme, properties: null);
} }
public abstract void SignIn(string authenticationScheme, ClaimsPrincipal principal, AuthenticationProperties properties); // Leave it up to authentication handler to do the right thing for the challenge
public virtual Task ChallengeAsync([NotNull] string authenticationScheme, AuthenticationProperties properties)
public virtual void SignOut()
{ {
SignOut(authenticationScheme: null, properties: null); return ChallengeAsync(authenticationScheme, properties, ChallengeBehavior.Automatic);
} }
public abstract void SignOut(string authenticationScheme); public virtual Task SignInAsync([NotNull] string authenticationScheme, [NotNull] ClaimsPrincipal principal)
{
return SignInAsync(authenticationScheme, principal, properties: null);
}
public abstract void SignOut(string authenticationScheme, AuthenticationProperties properties); public virtual Task ForbidAsync([NotNull] string authenticationScheme)
{
return ForbidAsync(authenticationScheme, properties: null);
}
// Deny access (typically a 403)
public virtual Task ForbidAsync([NotNull] string authenticationScheme, AuthenticationProperties properties)
{
return ChallengeAsync(authenticationScheme, properties, ChallengeBehavior.Forbidden);
}
public abstract Task ChallengeAsync([NotNull] string authenticationScheme, AuthenticationProperties properties, ChallengeBehavior behavior);
public abstract Task SignInAsync([NotNull] string authenticationScheme, [NotNull] ClaimsPrincipal principal, AuthenticationProperties properties);
public virtual Task SignOutAsync([NotNull] string authenticationScheme)
{
return SignOutAsync(authenticationScheme, properties: null);
}
public abstract Task SignOutAsync([NotNull] string authenticationScheme, AuthenticationProperties properties);
} }
} }

View File

@ -3,11 +3,20 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading.Tasks;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Http namespace Microsoft.AspNet.Http
{ {
public abstract class HttpResponse public abstract class HttpResponse
{ {
private static readonly Func<object, Task> _callbackDelegate = callback => ((Func<Task>)callback)();
private static readonly Func<object, Task> _disposeDelegate = disposable =>
{
((IDisposable)disposable).Dispose();
return Task.FromResult(0);
};
public abstract HttpContext HttpContext { get; } public abstract HttpContext HttpContext { get; }
public abstract int StatusCode { get; set; } public abstract int StatusCode { get; set; }
@ -24,14 +33,17 @@ namespace Microsoft.AspNet.Http
public abstract bool HasStarted { get; } public abstract bool HasStarted { get; }
public abstract void OnResponseStarting(Action<object> callback, object state); public abstract void OnStarting([NotNull] Func<object, Task> callback, object state);
public abstract void OnResponseCompleted(Action<object> callback, object state); public virtual void OnStarting([NotNull] Func<Task> callback) => OnStarting(_callbackDelegate, callback);
public virtual void Redirect(string location) public abstract void OnCompleted([NotNull] Func<object, Task> callback, object state);
{
Redirect(location, permanent: false); public virtual void OnCompletedDispose([NotNull] IDisposable disposable) => OnCompleted(_disposeDelegate, disposable);
}
public virtual void OnCompleted([NotNull] Func<Task> callback) => OnCompleted(_callbackDelegate, callback);
public virtual void Redirect(string location) => Redirect(location, permanent: false);
public abstract void Redirect(string location, bool permanent); public abstract void Redirect(string location, bool permanent);
} }

View File

@ -0,0 +1,12 @@
// 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.
namespace Microsoft.AspNet.Http.Features.Authentication
{
public enum ChallengeBehavior
{
Automatic,
Unauthorized,
Forbidden
}
}

View File

@ -3,19 +3,27 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Http.Features.Authentication namespace Microsoft.AspNet.Http.Features.Authentication
{ {
public class ChallengeContext public class ChallengeContext
{ {
public ChallengeContext(string authenticationScheme, IDictionary<string, string> properties) public ChallengeContext([NotNull] string authenticationScheme) : this(authenticationScheme, properties: null, behavior: ChallengeBehavior.Automatic)
{
}
public ChallengeContext([NotNull] string authenticationScheme, IDictionary<string, string> properties, ChallengeBehavior behavior)
{ {
AuthenticationScheme = authenticationScheme; AuthenticationScheme = authenticationScheme;
Properties = properties ?? new Dictionary<string, string>(StringComparer.Ordinal); Properties = properties ?? new Dictionary<string, string>(StringComparer.Ordinal);
Behavior = behavior;
} }
public string AuthenticationScheme { get; } public string AuthenticationScheme { get; }
public ChallengeBehavior Behavior { get; }
public IDictionary<string, string> Properties { get; } public IDictionary<string, string> Properties { get; }
public bool Accepted { get; private set; } public bool Accepted { get; private set; }

View File

@ -9,14 +9,12 @@ namespace Microsoft.AspNet.Http.Features.Authentication
{ {
void GetDescriptions(DescribeSchemesContext context); void GetDescriptions(DescribeSchemesContext context);
void Authenticate(AuthenticateContext context);
Task AuthenticateAsync(AuthenticateContext context); Task AuthenticateAsync(AuthenticateContext context);
void Challenge(ChallengeContext context); Task ChallengeAsync(ChallengeContext context);
void SignIn(SignInContext context); Task SignInAsync(SignInContext context);
void SignOut(SignOutContext context); Task SignOutAsync(SignOutContext context);
} }
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Http.Features namespace Microsoft.AspNet.Http.Features
{ {
@ -14,7 +15,7 @@ namespace Microsoft.AspNet.Http.Features
IDictionary<string, string[]> Headers { get; set; } IDictionary<string, string[]> Headers { get; set; }
Stream Body { get; set; } Stream Body { get; set; }
bool HasStarted { get; } bool HasStarted { get; }
void OnResponseStarting(Action<object> callback, object state); void OnStarting(Func<object, Task> callback, object state);
void OnResponseCompleted(Action<object> callback, object state); void OnCompleted(Func<object, Task> callback, object state);
} }
} }

View File

@ -48,66 +48,29 @@ namespace Microsoft.AspNet.Http.Authentication.Internal
return describeContext.Results.Select(description => new AuthenticationDescription(description)); return describeContext.Results.Select(description => new AuthenticationDescription(description));
} }
public override AuthenticationResult Authenticate([NotNull] string authenticationScheme) public override async Task AuthenticateAsync([NotNull] AuthenticateContext context)
{ {
var handler = HttpAuthenticationFeature.Handler; var handler = HttpAuthenticationFeature.Handler;
var authenticateContext = new AuthenticateContext(authenticationScheme);
if (handler != null) if (handler != null)
{ {
handler.Authenticate(authenticateContext); await handler.AuthenticateAsync(context);
} }
if (!authenticateContext.Accepted) if (!context.Accepted)
{ {
throw new InvalidOperationException($"The following authentication scheme was not accepted: {authenticationScheme}"); throw new InvalidOperationException($"The following authentication scheme was not accepted: {context.AuthenticationScheme}");
} }
if (authenticateContext.Principal == null)
{
return null;
}
return new AuthenticationResult(authenticateContext.Principal,
new AuthenticationProperties(authenticateContext.Properties),
new AuthenticationDescription(authenticateContext.Description));
} }
public override async Task<AuthenticationResult> AuthenticateAsync([NotNull] string authenticationScheme) public override async Task ChallengeAsync([NotNull] string authenticationScheme, AuthenticationProperties properties, ChallengeBehavior behavior)
{ {
var handler = HttpAuthenticationFeature.Handler; var handler = HttpAuthenticationFeature.Handler;
var authenticateContext = new AuthenticateContext(authenticationScheme); var challengeContext = new ChallengeContext(authenticationScheme, properties?.Items, behavior);
if (handler != null) if (handler != null)
{ {
await handler.AuthenticateAsync(authenticateContext); await handler.ChallengeAsync(challengeContext);
}
// Verify all types ack'd
if (!authenticateContext.Accepted)
{
throw new InvalidOperationException($"The following authentication scheme was not accepted: {authenticationScheme}");
}
if (authenticateContext.Principal == null)
{
return null;
}
return new AuthenticationResult(authenticateContext.Principal,
new AuthenticationProperties(authenticateContext.Properties),
new AuthenticationDescription(authenticateContext.Description));
}
public override void Challenge(string authenticationScheme, AuthenticationProperties properties)
{
HttpResponseFeature.StatusCode = 401;
var handler = HttpAuthenticationFeature.Handler;
var challengeContext = new ChallengeContext(authenticationScheme, properties?.Items);
if (handler != null)
{
handler.Challenge(challengeContext);
} }
// The default Challenge with no scheme is always accepted // The default Challenge with no scheme is always accepted
@ -117,43 +80,36 @@ namespace Microsoft.AspNet.Http.Authentication.Internal
} }
} }
public override void SignIn([NotNull] string authenticationScheme, [NotNull] ClaimsPrincipal principal, AuthenticationProperties properties) public override async Task SignInAsync([NotNull] string authenticationScheme, [NotNull] ClaimsPrincipal principal, AuthenticationProperties properties)
{ {
var handler = HttpAuthenticationFeature.Handler; var handler = HttpAuthenticationFeature.Handler;
var signInContext = new SignInContext(authenticationScheme, principal, properties?.Items); var signInContext = new SignInContext(authenticationScheme, principal, properties?.Items);
if (handler != null) if (handler != null)
{ {
handler.SignIn(signInContext); await handler.SignInAsync(signInContext);
} }
// Verify all types ack'd
if (!signInContext.Accepted) if (!signInContext.Accepted)
{ {
throw new InvalidOperationException($"The following authentication scheme was not accepted: {authenticationScheme}"); throw new InvalidOperationException($"The following authentication scheme was not accepted: {authenticationScheme}");
} }
} }
public override void SignOut(string authenticationScheme, AuthenticationProperties properties) public override async Task SignOutAsync([NotNull] string authenticationScheme, AuthenticationProperties properties)
{ {
var handler = HttpAuthenticationFeature.Handler; var handler = HttpAuthenticationFeature.Handler;
var signOutContext = new SignOutContext(authenticationScheme, properties?.Items); var signOutContext = new SignOutContext(authenticationScheme, properties?.Items);
if (handler != null) if (handler != null)
{ {
handler.SignOut(signOutContext); await handler.SignOutAsync(signOutContext);
} }
// Verify all types ack'd
if (!string.IsNullOrWhiteSpace(authenticationScheme) && !signOutContext.Accepted) if (!string.IsNullOrWhiteSpace(authenticationScheme) && !signOutContext.Accepted)
{ {
throw new InvalidOperationException($"The following authentication scheme was not accepted: {authenticationScheme}"); throw new InvalidOperationException($"The following authentication scheme was not accepted: {authenticationScheme}");
} }
} }
public override void SignOut(string authenticationScheme)
{
SignOut(authenticationScheme, properties: null);
}
} }
} }

View File

@ -3,6 +3,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNet.FeatureModel; using Microsoft.AspNet.FeatureModel;
using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Http.Features.Internal; using Microsoft.AspNet.Http.Features.Internal;
@ -94,14 +95,14 @@ namespace Microsoft.AspNet.Http.Internal
get { return HttpResponseFeature.HasStarted; } get { return HttpResponseFeature.HasStarted; }
} }
public override void OnResponseStarting(Action<object> callback, object state) public override void OnStarting(Func<object, Task> callback, object state)
{ {
HttpResponseFeature.OnResponseStarting(callback, state); HttpResponseFeature.OnStarting(callback, state);
} }
public override void OnResponseCompleted(Action<object> callback, object state) public override void OnCompleted(Func<object, Task> callback, object state)
{ {
HttpResponseFeature.OnResponseCompleted(callback, state); HttpResponseFeature.OnCompleted(callback, state);
} }
public override void Redirect(string location, bool permanent) public override void Redirect(string location, bool permanent)

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Http.Features.Internal namespace Microsoft.AspNet.Http.Features.Internal
{ {
@ -29,14 +30,14 @@ namespace Microsoft.AspNet.Http.Features.Internal
get { return false; } get { return false; }
} }
public void OnResponseStarting(Action<object> callback, object state) public void OnStarting(Func<object, Task> callback, object state)
{ {
throw new NotSupportedException(); throw new NotImplementedException();
} }
public void OnResponseCompleted(Action<object> callback, object state) public void OnCompleted(Func<object, Task> callback, object state)
{ {
throw new NotSupportedException(); throw new NotImplementedException();
} }
} }
} }

View File

@ -62,7 +62,15 @@ namespace Microsoft.AspNet.Owin
{ OwinConstants.ResponseReasonPhrase, new FeatureMap<IHttpResponseFeature>(feature => feature.ReasonPhrase, (feature, value) => feature.ReasonPhrase = Convert.ToString(value)) }, { OwinConstants.ResponseReasonPhrase, new FeatureMap<IHttpResponseFeature>(feature => feature.ReasonPhrase, (feature, value) => feature.ReasonPhrase = Convert.ToString(value)) },
{ OwinConstants.ResponseHeaders, new FeatureMap<IHttpResponseFeature>(feature => feature.Headers, (feature, value) => feature.Headers = (IDictionary<string, string[]>)value) }, { OwinConstants.ResponseHeaders, new FeatureMap<IHttpResponseFeature>(feature => feature.Headers, (feature, value) => feature.Headers = (IDictionary<string, string[]>)value) },
{ OwinConstants.ResponseBody, new FeatureMap<IHttpResponseFeature>(feature => feature.Body, () => Stream.Null, (feature, value) => feature.Body = (Stream)value) }, { OwinConstants.ResponseBody, new FeatureMap<IHttpResponseFeature>(feature => feature.Body, () => Stream.Null, (feature, value) => feature.Body = (Stream)value) },
{ OwinConstants.CommonKeys.OnSendingHeaders, new FeatureMap<IHttpResponseFeature>(feature => new Action<Action<object>, object>(feature.OnResponseStarting)) }, { OwinConstants.CommonKeys.OnSendingHeaders, new FeatureMap<IHttpResponseFeature>(
feature => new Action<Action<object>, object>((cb, state) => {
feature.OnStarting(s =>
{
cb(s);
return Task.FromResult(0);
}, state);
}))
},
{ OwinConstants.CommonKeys.LocalPort, new FeatureMap<IHttpConnectionFeature>(feature => feature.LocalPort.ToString(CultureInfo.InvariantCulture), { OwinConstants.CommonKeys.LocalPort, new FeatureMap<IHttpConnectionFeature>(feature => feature.LocalPort.ToString(CultureInfo.InvariantCulture),
(feature, value) => feature.LocalPort = Convert.ToInt32(value, CultureInfo.InvariantCulture)) }, (feature, value) => feature.LocalPort = Convert.ToInt32(value, CultureInfo.InvariantCulture)) },

View File

@ -147,17 +147,19 @@ namespace Microsoft.AspNet.Owin
get { return _headersSent; } get { return _headersSent; }
} }
void IHttpResponseFeature.OnResponseStarting(Action<object> callback, object state) void IHttpResponseFeature.OnStarting(Func<object, Task> callback, object state)
{ {
var register = Prop<Action<Action<object>, object>>(OwinConstants.CommonKeys.OnSendingHeaders); var register = Prop<Action<Action<object>, object>>(OwinConstants.CommonKeys.OnSendingHeaders);
if (register == null) if (register == null)
{ {
throw new NotSupportedException(OwinConstants.CommonKeys.OnSendingHeaders); throw new NotSupportedException(OwinConstants.CommonKeys.OnSendingHeaders);
} }
register(callback, state);
// Need to block on the callback since we can't change the OWIN signature to be async
register(s => callback(s).GetAwaiter().GetResult(), state);
} }
void IHttpResponseFeature.OnResponseCompleted(Action<object> callback, object state) void IHttpResponseFeature.OnCompleted(Func<object, Task> callback, object state)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }

View File

@ -18,50 +18,46 @@ namespace Microsoft.AspNet.Http.Authentication.Internal
public async Task AuthenticateWithNoAuthMiddlewareThrows() public async Task AuthenticateWithNoAuthMiddlewareThrows()
{ {
var context = CreateContext(); var context = CreateContext();
Assert.Throws<InvalidOperationException>(() => context.Authentication.Authenticate("Foo"));
await Assert.ThrowsAsync<InvalidOperationException>(async () => await context.Authentication.AuthenticateAsync("Foo")); await Assert.ThrowsAsync<InvalidOperationException>(async () => await context.Authentication.AuthenticateAsync("Foo"));
} }
[Fact] [Fact]
public void ChallengeWithNoAuthMiddlewareMayThrow() public async Task ChallengeWithNoAuthMiddlewareMayThrow()
{ {
var context = CreateContext(); var context = CreateContext();
context.Authentication.Challenge(); await context.Authentication.ChallengeAsync();
Assert.Equal(401, context.Response.StatusCode); Assert.Equal(200, context.Response.StatusCode);
await Assert.ThrowsAsync<InvalidOperationException>(() => context.Authentication.ChallengeAsync("Foo"));
Assert.Throws<InvalidOperationException>(() => context.Authentication.Challenge("Foo"));
} }
[Fact] [Fact]
public void SignInWithNoAuthMiddlewareThrows() public async Task SignInWithNoAuthMiddlewareThrows()
{ {
var context = CreateContext(); var context = CreateContext();
Assert.Throws<InvalidOperationException>(() => context.Authentication.SignIn("Foo", new ClaimsPrincipal())); await Assert.ThrowsAsync<InvalidOperationException>(() => context.Authentication.SignInAsync("Foo", new ClaimsPrincipal()));
} }
[Fact] [Fact]
public void SignOutWithNoAuthMiddlewareMayThrow() public async Task SignOutWithNoAuthMiddlewareMayThrow()
{ {
var context = CreateContext(); var context = CreateContext();
context.Authentication.SignOut(); await Assert.ThrowsAsync<InvalidOperationException>(() => context.Authentication.SignOutAsync("Foo"));
Assert.Throws<InvalidOperationException>(() => context.Authentication.SignOut("Foo"));
} }
[Fact] [Fact]
public void SignInOutIn() public async Task SignInOutIn()
{ {
var context = CreateContext(); var context = CreateContext();
var handler = new AuthHandler(); var handler = new AuthHandler();
context.SetFeature<IHttpAuthenticationFeature>(new HttpAuthenticationFeature() { Handler = handler }); context.SetFeature<IHttpAuthenticationFeature>(new HttpAuthenticationFeature() { Handler = handler });
var user = new ClaimsPrincipal(); var user = new ClaimsPrincipal();
context.Authentication.SignIn("ignored", user); await context.Authentication.SignInAsync("ignored", user);
Assert.True(handler.SignedIn); Assert.True(handler.SignedIn);
context.Authentication.SignOut("ignored"); await context.Authentication.SignOutAsync("ignored");
Assert.False(handler.SignedIn); Assert.False(handler.SignedIn);
context.Authentication.SignIn("ignored", user); await context.Authentication.SignInAsync("ignored", user);
Assert.True(handler.SignedIn); Assert.True(handler.SignedIn);
context.Authentication.SignOut("ignored", new AuthenticationProperties() { RedirectUri = "~/logout" }); await context.Authentication.SignOutAsync("ignored", new AuthenticationProperties() { RedirectUri = "~/logout" });
Assert.False(handler.SignedIn); Assert.False(handler.SignedIn);
} }
@ -69,17 +65,12 @@ namespace Microsoft.AspNet.Http.Authentication.Internal
{ {
public bool SignedIn { get; set; } public bool SignedIn { get; set; }
public void Authenticate(AuthenticateContext context)
{
throw new NotImplementedException();
}
public Task AuthenticateAsync(AuthenticateContext context) public Task AuthenticateAsync(AuthenticateContext context)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void Challenge(ChallengeContext context) public Task ChallengeAsync(ChallengeContext context)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@ -89,16 +80,18 @@ namespace Microsoft.AspNet.Http.Authentication.Internal
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void SignIn(SignInContext context) public Task SignInAsync(SignInContext context)
{ {
SignedIn = true; SignedIn = true;
context.Accept(); context.Accept();
return Task.FromResult(0);
} }
public void SignOut(SignOutContext context) public Task SignOutAsync(SignOutContext context)
{ {
SignedIn = false; SignedIn = false;
context.Accept(); context.Accept();
return Task.FromResult(0);
} }
} }