From e8a6aab0fa0c9647e6be5f57de201f7df421ee14 Mon Sep 17 00:00:00 2001 From: Jerrie Pelser Date: Thu, 21 Aug 2014 08:31:01 +0700 Subject: [PATCH] Add Buffer provider --- .../Buffer/BufferAuthenticationExtensions.cs | 29 +++ .../Buffer/BufferAuthenticationHandler.cs | 222 ++++++++++++++++++ .../Buffer/BufferAuthenticationMiddleware.cs | 85 +++++++ .../Buffer/BufferAuthenticationOptions.cs | 92 ++++++++ Owin.Security.Providers/Buffer/Constants.cs | 7 + .../Provider/BufferAuthenticatedContext.cs | 86 +++++++ .../Provider/BufferAuthenticationProvider.cs | 50 ++++ .../Provider/BufferReturnEndpointContext.cs | 26 ++ .../Provider/IBufferAuthenticationProvider.cs | 24 ++ .../Owin.Security.Providers.csproj | 9 + ...Owin.Security.Providers.csproj.DotSettings | 3 + ...-OwinOAuthProvidersDemo-20131113093838.mdf | Bin 3211264 -> 3211264 bytes ...nOAuthProvidersDemo-20131113093838_log.ldf | Bin 1048576 -> 1048576 bytes .../App_Start/Startup.Auth.cs | 3 + 14 files changed, 636 insertions(+) create mode 100644 Owin.Security.Providers/Buffer/BufferAuthenticationExtensions.cs create mode 100644 Owin.Security.Providers/Buffer/BufferAuthenticationHandler.cs create mode 100644 Owin.Security.Providers/Buffer/BufferAuthenticationMiddleware.cs create mode 100644 Owin.Security.Providers/Buffer/BufferAuthenticationOptions.cs create mode 100644 Owin.Security.Providers/Buffer/Constants.cs create mode 100644 Owin.Security.Providers/Buffer/Provider/BufferAuthenticatedContext.cs create mode 100644 Owin.Security.Providers/Buffer/Provider/BufferAuthenticationProvider.cs create mode 100644 Owin.Security.Providers/Buffer/Provider/BufferReturnEndpointContext.cs create mode 100644 Owin.Security.Providers/Buffer/Provider/IBufferAuthenticationProvider.cs create mode 100644 Owin.Security.Providers/Owin.Security.Providers.csproj.DotSettings diff --git a/Owin.Security.Providers/Buffer/BufferAuthenticationExtensions.cs b/Owin.Security.Providers/Buffer/BufferAuthenticationExtensions.cs new file mode 100644 index 0000000..56e21dc --- /dev/null +++ b/Owin.Security.Providers/Buffer/BufferAuthenticationExtensions.cs @@ -0,0 +1,29 @@ +using System; + +namespace Owin.Security.Providers.Buffer +{ + public static class BufferAuthenticationExtensions + { + public static IAppBuilder UseBufferAuthentication(this IAppBuilder app, + BufferAuthenticationOptions options) + { + if (app == null) + throw new ArgumentNullException("app"); + if (options == null) + throw new ArgumentNullException("options"); + + app.Use(typeof(BufferAuthenticationMiddleware), app, options); + + return app; + } + + public static IAppBuilder UseBufferAuthentication(this IAppBuilder app, string clientId, string clientSecret) + { + return app.UseBufferAuthentication(new BufferAuthenticationOptions + { + ClientId = clientId, + ClientSecret = clientSecret + }); + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Buffer/BufferAuthenticationHandler.cs b/Owin.Security.Providers/Buffer/BufferAuthenticationHandler.cs new file mode 100644 index 0000000..2837980 --- /dev/null +++ b/Owin.Security.Providers/Buffer/BufferAuthenticationHandler.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.Owin; +using Microsoft.Owin.Infrastructure; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Infrastructure; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Owin.Security.Providers.Buffer +{ + public class BufferAuthenticationHandler : AuthenticationHandler + { + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + private const string TokenEndpoint = "https://api.bufferapp.com/1/oauth2/token.json"; + private const string UserInfoEndpoint = "https://api.bufferapp.com/1/user.json"; + + private readonly ILogger logger; + private readonly HttpClient httpClient; + + public BufferAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + this.httpClient = httpClient; + this.logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + string state = null; + + IReadableStringCollection query = Request.Query; + IList values = query.GetValues("code"); + if (values != null && values.Count == 1) + { + code = values[0]; + } + values = query.GetValues("state"); + if (values != null && values.Count == 1) + { + state = values[0]; + } + + properties = Options.StateDataFormat.Unprotect(state); + if (properties == null) + { + return null; + } + + // OAuth2 10.12 CSRF + if (!ValidateCorrelationId(properties, logger)) + { + return new AuthenticationTicket(null, properties); + } + + string requestPrefix = Request.Scheme + "://" + Request.Host; + string redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List>(); + body.Add(new KeyValuePair("grant_type", "authorization_code")); + body.Add(new KeyValuePair("code", code)); + body.Add(new KeyValuePair("redirect_uri", redirectUri)); + body.Add(new KeyValuePair("client_id", Options.ClientId)); + body.Add(new KeyValuePair("client_secret", Options.ClientSecret)); + + // Request the token + HttpResponseMessage tokenResponse = + await httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); + tokenResponse.EnsureSuccessStatusCode(); + string text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + string accessToken = (string)response.access_token; + string expires = (string) response.expires_in; + + // Get the Buffer user + HttpResponseMessage graphResponse = await httpClient.GetAsync( + UserInfoEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken), Request.CallCancelled); + graphResponse.EnsureSuccessStatusCode(); + text = await graphResponse.Content.ReadAsStringAsync(); + JObject user = JObject.Parse(text); + + var context = new BufferAuthenticatedContext(Context, user, accessToken, expires); + context.Identity = new ClaimsIdentity( + Options.AuthenticationType, + ClaimsIdentity.DefaultNameClaimType, + ClaimsIdentity.DefaultRoleClaimType); + if (!string.IsNullOrEmpty(context.Id)) + { + context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.Id, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.Name)) + { + context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, XmlSchemaString, Options.AuthenticationType)); + } + context.Properties = properties; + + await Options.Provider.Authenticated(context); + + return new AuthenticationTicket(context.Identity, context.Properties); + } + catch (Exception ex) + { + logger.WriteError(ex.Message); + } + return new AuthenticationTicket(null, properties); + } + + protected override Task ApplyResponseChallengeAsync() + { + if (Response.StatusCode != 401) + { + return Task.FromResult(null); + } + + AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge != null) + { + string baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + string currentUri = + baseUri + + Request.Path + + Request.QueryString; + + string redirectUri = + baseUri + + Options.CallbackPath; + + AuthenticationProperties properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + string state = Options.StateDataFormat.Protect(properties); + + string authorizationEndpoint = + "https://bufferapp.com/oauth2/authorize" + + "?response_type=code" + + "&client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&state=" + Uri.EscapeDataString(state); + + Response.Redirect(authorizationEndpoint); + } + + return Task.FromResult(null); + } + + public override async Task InvokeAsync() + { + return await InvokeReplyPathAsync(); + } + + private async Task InvokeReplyPathAsync() + { + if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path) + { + // TODO: error responses + + AuthenticationTicket ticket = await AuthenticateAsync(); + if (ticket == null) + { + logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new BufferReturnEndpointContext(Context, ticket); + context.SignInAsAuthenticationType = Options.SignInAsAuthenticationType; + context.RedirectUri = ticket.Properties.RedirectUri; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + ClaimsIdentity grantIdentity = context.Identity; + if (!string.Equals(grantIdentity.AuthenticationType, context.SignInAsAuthenticationType, StringComparison.Ordinal)) + { + grantIdentity = new ClaimsIdentity(grantIdentity.Claims, context.SignInAsAuthenticationType, grantIdentity.NameClaimType, grantIdentity.RoleClaimType); + } + Context.Authentication.SignIn(context.Properties, grantIdentity); + } + + if (!context.IsRequestCompleted && context.RedirectUri != null) + { + string redirectUri = context.RedirectUri; + if (context.Identity == null) + { + // add a redirect hint that sign-in failed in some way + redirectUri = WebUtilities.AddQueryString(redirectUri, "error", "access_denied"); + } + Response.Redirect(redirectUri); + context.RequestCompleted(); + } + + return context.IsRequestCompleted; + } + return false; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Buffer/BufferAuthenticationMiddleware.cs b/Owin.Security.Providers/Buffer/BufferAuthenticationMiddleware.cs new file mode 100644 index 0000000..f081de3 --- /dev/null +++ b/Owin.Security.Providers/Buffer/BufferAuthenticationMiddleware.cs @@ -0,0 +1,85 @@ +using System; +using System.Globalization; +using System.Net.Http; +using Microsoft.Owin; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.DataHandler; +using Microsoft.Owin.Security.DataProtection; +using Microsoft.Owin.Security.Infrastructure; +using Owin.Security.Providers.Properties; + +namespace Owin.Security.Providers.Buffer +{ + public class BufferAuthenticationMiddleware : AuthenticationMiddleware + { + private readonly HttpClient httpClient; + private readonly ILogger logger; + + public BufferAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, + BufferAuthenticationOptions options) + : base(next, options) + { + if (String.IsNullOrWhiteSpace(Options.ClientId)) + throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + Resources.Exception_OptionMustBeProvided, "ClientId")); + if (String.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + Resources.Exception_OptionMustBeProvided, "ClientSecret")); + + logger = app.CreateLogger(); + + if (Options.Provider == null) + Options.Provider = new BufferAuthenticationProvider(); + + if (Options.StateDataFormat == null) + { + IDataProtector dataProtector = app.CreateDataProtector( + typeof (BufferAuthenticationMiddleware).FullName, + Options.AuthenticationType, "v1"); + Options.StateDataFormat = new PropertiesDataFormat(dataProtector); + } + + if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); + + httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + { + Timeout = Options.BackchannelTimeout, + MaxResponseContentBufferSize = 1024*1024*10 + }; + } + + /// + /// Provides the object for processing + /// authentication-related requests. + /// + /// + /// An configured with the + /// supplied to the constructor. + /// + protected override AuthenticationHandler CreateHandler() + { + return new BufferAuthenticationHandler(httpClient, logger); + } + + private HttpMessageHandler ResolveHttpMessageHandler(BufferAuthenticationOptions options) + { + HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + + // If they provided a validator, apply it or fail. + if (options.BackchannelCertificateValidator != null) + { + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) + { + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); + } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + } + + return handler; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Buffer/BufferAuthenticationOptions.cs b/Owin.Security.Providers/Buffer/BufferAuthenticationOptions.cs new file mode 100644 index 0000000..67569fa --- /dev/null +++ b/Owin.Security.Providers/Buffer/BufferAuthenticationOptions.cs @@ -0,0 +1,92 @@ +using System; +using System.Net.Http; +using Microsoft.Owin; +using Microsoft.Owin.Security; + +namespace Owin.Security.Providers.Buffer +{ + public class BufferAuthenticationOptions : AuthenticationOptions + { + /// + /// Gets or sets the a pinned certificate validator to use to validate the endpoints used + /// in back channel communications belong to Buffer + /// + /// + /// The pinned certificate validator. + /// + /// + /// If this property is null then the default certificate checks are performed, + /// validating the subject name and if the signing chain is a trusted party. + /// + public ICertificateValidator BackchannelCertificateValidator { get; set; } + + /// + /// The HttpMessageHandler used to communicate with Buffer. + /// This cannot be set at the same time as BackchannelCertificateValidator unless the value + /// can be downcast to a WebRequestHandler. + /// + public HttpMessageHandler BackchannelHttpHandler { get; set; } + + /// + /// Gets or sets timeout value in milliseconds for back channel communications with Buffer. + /// + /// + /// The back channel timeout in milliseconds. + /// + public TimeSpan BackchannelTimeout { get; set; } + + /// + /// 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-buffer". + /// + public PathString CallbackPath { get; set; } + + /// + /// Get or sets the text that the user can display on a sign in user interface. + /// + public string Caption + { + get { return Description.Caption; } + set { Description.Caption = value; } + } + + /// + /// Gets or sets the Buffer supplied Client ID + /// + public string ClientId { get; set; } + + /// + /// Gets or sets the Buffer supplied Client Secret + /// + public string ClientSecret { get; set; } + + /// + /// Gets or sets the used in the authentication events + /// + public IBufferAuthenticationProvider Provider { get; set; } + + /// + /// Gets or sets the name of another authentication middleware which will be responsible for actually issuing a user + /// . + /// + public string SignInAsAuthenticationType { get; set; } + + /// + /// Gets or sets the type used to secure data handled by the middleware. + /// + public ISecureDataFormat StateDataFormat { get; set; } + + /// + /// Initializes a new + /// + public BufferAuthenticationOptions() + : base("Buffer") + { + Caption = Constants.DefaultAuthenticationType; + CallbackPath = new PathString("/signin-buffer"); + AuthenticationMode = AuthenticationMode.Passive; + BackchannelTimeout = TimeSpan.FromSeconds(60); + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Buffer/Constants.cs b/Owin.Security.Providers/Buffer/Constants.cs new file mode 100644 index 0000000..93b477e --- /dev/null +++ b/Owin.Security.Providers/Buffer/Constants.cs @@ -0,0 +1,7 @@ +namespace Owin.Security.Providers.Buffer +{ + internal static class Constants + { + public const string DefaultAuthenticationType = "Buffer"; + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Buffer/Provider/BufferAuthenticatedContext.cs b/Owin.Security.Providers/Buffer/Provider/BufferAuthenticatedContext.cs new file mode 100644 index 0000000..ee8c9c5 --- /dev/null +++ b/Owin.Security.Providers/Buffer/Provider/BufferAuthenticatedContext.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.Security.Claims; +using Microsoft.Owin; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Provider; +using Newtonsoft.Json.Linq; + +namespace Owin.Security.Providers.Buffer +{ + /// + /// Contains information about the login session as well as the user . + /// + public class BufferAuthenticatedContext : BaseContext + { + /// + /// Initializes a + /// + /// The OWIN environment + /// The JSON-serialized user + /// Buffer Access token + /// Seconds until expiration + public BufferAuthenticatedContext(IOwinContext context, JObject user, string accessToken, string expires) + : base(context) + { + User = user; + Id = TryGetValue(user, "id"); + Name = TryGetValue(user, "name"); + AccessToken = accessToken; + + int expiresValue; + if (Int32.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue)) + { + ExpiresIn = TimeSpan.FromSeconds(expiresValue); + } + + Id = TryGetValue(user, "id"); + } + + /// + /// Gets the JSON-serialized user + /// + /// + /// Contains the Buffer user obtained from the endpoint https://api.bufferapp.com/1/user.json + /// + public JObject User { get; private set; } + + /// + /// Gets the Buffer OAuth access token + /// + public string AccessToken { get; private set; } + + /// + /// Gets the Buffer access token expiration time + /// + public TimeSpan? ExpiresIn { get; set; } + + /// + /// Gets the Buffer user ID + /// + public string Id { get; private set; } + + /// + /// The name of the user + /// + public string Name { get; private set; } + + /// + /// Gets the representing the user + /// + public ClaimsIdentity Identity { get; set; } + + /// + /// Gets or sets a property bag for common authentication properties + /// + public AuthenticationProperties Properties { get; set; } + + private static string TryGetValue(JObject user, string propertyName) + { + JToken value; + return user.TryGetValue(propertyName, out value) ? value.ToString() : null; + } + } +} diff --git a/Owin.Security.Providers/Buffer/Provider/BufferAuthenticationProvider.cs b/Owin.Security.Providers/Buffer/Provider/BufferAuthenticationProvider.cs new file mode 100644 index 0000000..49c6930 --- /dev/null +++ b/Owin.Security.Providers/Buffer/Provider/BufferAuthenticationProvider.cs @@ -0,0 +1,50 @@ +using System; +using System.Threading.Tasks; + +namespace Owin.Security.Providers.Buffer +{ + /// + /// Default implementation. + /// + public class BufferAuthenticationProvider : IBufferAuthenticationProvider + { + /// + /// Initializes a + /// + public BufferAuthenticationProvider() + { + OnAuthenticated = context => Task.FromResult(null); + OnReturnEndpoint = context => Task.FromResult(null); + } + + /// + /// Gets or sets the function that is invoked when the Authenticated method is invoked. + /// + public Func OnAuthenticated { get; set; } + + /// + /// Gets or sets the function that is invoked when the ReturnEndpoint method is invoked. + /// + public Func OnReturnEndpoint { get; set; } + + /// + /// Invoked whenever Buffer succesfully authenticates a user + /// + /// Contains information about the login session as well as the user . + /// A representing the completed operation. + public virtual Task Authenticated(BufferAuthenticatedContext context) + { + return OnAuthenticated(context); + } + + /// + /// Invoked prior to the being saved in a local cookie and the browser being redirected to the originally requested URL. + /// + /// + /// A representing the completed operation. + public virtual Task ReturnEndpoint(BufferReturnEndpointContext context) + { + return OnReturnEndpoint(context); + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Buffer/Provider/BufferReturnEndpointContext.cs b/Owin.Security.Providers/Buffer/Provider/BufferReturnEndpointContext.cs new file mode 100644 index 0000000..cf62472 --- /dev/null +++ b/Owin.Security.Providers/Buffer/Provider/BufferReturnEndpointContext.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using Microsoft.Owin; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Provider; + +namespace Owin.Security.Providers.Buffer +{ + /// + /// Provides context information to middleware providers. + /// + public class BufferReturnEndpointContext : ReturnEndpointContext + { + /// + /// + /// + /// OWIN environment + /// The authentication ticket + public BufferReturnEndpointContext( + IOwinContext context, + AuthenticationTicket ticket) + : base(context, ticket) + { + } + } +} diff --git a/Owin.Security.Providers/Buffer/Provider/IBufferAuthenticationProvider.cs b/Owin.Security.Providers/Buffer/Provider/IBufferAuthenticationProvider.cs new file mode 100644 index 0000000..23243ed --- /dev/null +++ b/Owin.Security.Providers/Buffer/Provider/IBufferAuthenticationProvider.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; + +namespace Owin.Security.Providers.Buffer +{ + /// + /// Specifies callback methods which the invokes to enable developer control over the authentication process. /> + /// + public interface IBufferAuthenticationProvider + { + /// + /// Invoked whenever Buffer succesfully authenticates a user + /// + /// Contains information about the login session as well as the user . + /// A representing the completed operation. + Task Authenticated(BufferAuthenticatedContext context); + + /// + /// Invoked prior to the being saved in a local cookie and the browser being redirected to the originally requested URL. + /// + /// + /// A representing the completed operation. + Task ReturnEndpoint(BufferReturnEndpointContext context); + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Owin.Security.Providers.csproj b/Owin.Security.Providers/Owin.Security.Providers.csproj index 493f3dc..ef7a3e9 100644 --- a/Owin.Security.Providers/Owin.Security.Providers.csproj +++ b/Owin.Security.Providers/Owin.Security.Providers.csproj @@ -57,6 +57,15 @@ + + + + + + + + + diff --git a/Owin.Security.Providers/Owin.Security.Providers.csproj.DotSettings b/Owin.Security.Providers/Owin.Security.Providers.csproj.DotSettings new file mode 100644 index 0000000..ff86f88 --- /dev/null +++ b/Owin.Security.Providers/Owin.Security.Providers.csproj.DotSettings @@ -0,0 +1,3 @@ + + True + True \ No newline at end of file diff --git a/OwinOAuthProvidersDemo/App_Data/aspnet-OwinOAuthProvidersDemo-20131113093838.mdf b/OwinOAuthProvidersDemo/App_Data/aspnet-OwinOAuthProvidersDemo-20131113093838.mdf index e3681c2474cb650981def2a16d38298d1ce511e7..394f6d7080a97f6fa8c7135ad09e92692dd03a38 100644 GIT binary patch delta 8418 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%0AOxY7g17>>fL3?^I4GJC!X;z4EA1@7w8n z)Q5cYeP_H|d~MzeUyHZIcf!}{;}SiIS&5;<(!`mGRf)BUjovlhLSKe&sn_9~=dJY2 zS1ObsY5#;hZEEiP^AeMe!R`hv`x09@AFSdQ+{b$~4ndY6_XQx=2rZ&{w^}ogu7oIE0OE zBs4nC@NN7{o)*5z)5st6tbq-F+B1*ubcT7(HG%iI^7$-RkXQH~?lgChYvNww&NxnXWcRm20)9#bM>T*pqB4`!*Z!Y-Crk5q1_k%rl8CVryON zU3>0ub-2E9iSBIH&x+tVI+#67wtFkHo~dQ3n3+r|6JoL$55qB?IOIBkcj7I$9zTlb z;|g5rD#3-$LY#q1oDN8_&!9Hcf*R2p{&$=j+85DFOuioX+^~BN!vsYdD2pe z*PC{;`j|JZo*|#P(-O$5Oq!Gb;J`BUI4SAkH;~r*Sb-c`z#ba+vqH$e*ou5r#isva z!lpgI4#=QBNxM`56M)}fP!J_D)mpj_7|pHhH)b?|Of5)TNWx#xtMgCc=D9sPO|UhM zMkq3p5+l?!ihQzwjgZzCSjA4ACM}Eg3N3?w64)TVN=rS+U#i#?795K9rcf0^lnzrL z1Gjo#deVI4&|KEZf6+I*bsk&m-LP;}SU7VrU8mIfQlCppHr#*}YPTQyyCLGGOGGT7BJ{PFy^58X$Vf70i}E^Y zSjGj$~*2{_=JKcAcShPGXm(wT3@Pz{b?3Fb_;$I zSK=EW8J6&A^d4G|7NIc8WKS_7J69@XS{NVG=}%28_nvlL;d~%rpZ(X$zuL}PCy5{P z>zD}Bg{Lux@#}#!zT;Pn@qLZivRZg*O8y#U!#<>2k%p%aZM-bNa4QzqF0L3%v}=Y0C)4(i2T%nbm1yy%d*}peLk#W5xb&xAwX7 z@6xg0ANcgDc>RARR!=kF?V=W%nW5hQJq8*xfO^(vb0$;4 hHQ`7)z#xDOfGYqp0j>nd0vHT11mG$)9LcV){vStT$X);d delta 13034 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 CrdJdI delta 2214 zcmZo@aA;_7m|(+l@%M@wlNp^9HYU7gRn%Z$U|<45WB?L_h%+#(+ML6-pJ-(fI81|x zGcc^)tRn<8ji9n-9*K4y2}U4h0%B$$W&vVWAZ7z%_U$|p9AB1C58A>hv01?B1LG($ s8U~~3U^E+y76qdv;b=)XWJ};AuO++jf9&*#UkB04KLE?f?J) diff --git a/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs b/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs index 7535bc6..2b5953a 100755 --- a/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs +++ b/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs @@ -3,6 +3,7 @@ using Microsoft.AspNet.Identity; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Owin; +using Owin.Security.Providers.Buffer; using Owin.Security.Providers.GitHub; using Owin.Security.Providers.GooglePlus; using Owin.Security.Providers.GooglePlus.Provider; @@ -54,6 +55,8 @@ namespace OwinOAuthProvidersDemo //app.UseGitHubAuthentication("", ""); + //app.UseBufferAuthentication("", ""); + //app.UseStackExchangeAuthentication( // clientId: "", // clientSecret: "",