From e04540e22e56e72d35248fe3ba6e675f91cfb5d3 Mon Sep 17 00:00:00 2001 From: Jerrie Pelser Date: Mon, 3 Nov 2014 21:05:34 +0700 Subject: [PATCH] Add Dropbox provider --- Owin.Security.Providers/Dropbox/Constants.cs | 7 + .../DropboxAuthenticationExtensions.cs | 29 +++ .../Dropbox/DropboxAuthenticationHandler.cs | 229 ++++++++++++++++++ .../DropboxAuthenticationMiddleware.cs | 85 +++++++ .../Dropbox/DropboxAuthenticationOptions.cs | 92 +++++++ .../Provider/DropboxAuthenticatedContext.cs | 73 ++++++ .../Provider/DropboxAuthenticationProvider.cs | 50 ++++ .../Provider/DropboxReturnEndpointContext.cs | 26 ++ .../IDropboxAuthenticationProvider.cs | 24 ++ .../Owin.Security.Providers.csproj | 9 + ...Owin.Security.Providers.csproj.DotSettings | 2 + ...-OwinOAuthProvidersDemo-20131113093838.mdf | Bin 3211264 -> 3211264 bytes ...nOAuthProvidersDemo-20131113093838_log.ldf | Bin 1048576 -> 1048576 bytes .../App_Start/Startup.Auth.cs | 11 +- 14 files changed, 634 insertions(+), 3 deletions(-) create mode 100644 Owin.Security.Providers/Dropbox/Constants.cs create mode 100644 Owin.Security.Providers/Dropbox/DropboxAuthenticationExtensions.cs create mode 100644 Owin.Security.Providers/Dropbox/DropboxAuthenticationHandler.cs create mode 100644 Owin.Security.Providers/Dropbox/DropboxAuthenticationMiddleware.cs create mode 100644 Owin.Security.Providers/Dropbox/DropboxAuthenticationOptions.cs create mode 100644 Owin.Security.Providers/Dropbox/Provider/DropboxAuthenticatedContext.cs create mode 100644 Owin.Security.Providers/Dropbox/Provider/DropboxAuthenticationProvider.cs create mode 100644 Owin.Security.Providers/Dropbox/Provider/DropboxReturnEndpointContext.cs create mode 100644 Owin.Security.Providers/Dropbox/Provider/IDropboxAuthenticationProvider.cs diff --git a/Owin.Security.Providers/Dropbox/Constants.cs b/Owin.Security.Providers/Dropbox/Constants.cs new file mode 100644 index 0000000..09904c5 --- /dev/null +++ b/Owin.Security.Providers/Dropbox/Constants.cs @@ -0,0 +1,7 @@ +namespace Owin.Security.Providers.Dropbox +{ + internal static class Constants + { + public const string DefaultAuthenticationType = "Dropbox"; + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Dropbox/DropboxAuthenticationExtensions.cs b/Owin.Security.Providers/Dropbox/DropboxAuthenticationExtensions.cs new file mode 100644 index 0000000..7fca07e --- /dev/null +++ b/Owin.Security.Providers/Dropbox/DropboxAuthenticationExtensions.cs @@ -0,0 +1,29 @@ +using System; + +namespace Owin.Security.Providers.Dropbox +{ + public static class DropboxAuthenticationExtensions + { + public static IAppBuilder UseDropboxAuthentication(this IAppBuilder app, + DropboxAuthenticationOptions options) + { + if (app == null) + throw new ArgumentNullException("app"); + if (options == null) + throw new ArgumentNullException("options"); + + app.Use(typeof(DropboxAuthenticationMiddleware), app, options); + + return app; + } + + public static IAppBuilder UseDropboxAuthentication(this IAppBuilder app, string appKey, string appSecret) + { + return app.UseDropboxAuthentication(new DropboxAuthenticationOptions + { + AppKey = appKey, + AppSecret = appSecret + }); + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Dropbox/DropboxAuthenticationHandler.cs b/Owin.Security.Providers/Dropbox/DropboxAuthenticationHandler.cs new file mode 100644 index 0000000..83614d2 --- /dev/null +++ b/Owin.Security.Providers/Dropbox/DropboxAuthenticationHandler.cs @@ -0,0 +1,229 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +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.DataHandler.Encoder; +using Microsoft.Owin.Security.Infrastructure; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Owin.Security.Providers.Dropbox +{ + public class DropboxAuthenticationHandler : AuthenticationHandler + { + private const string StateCookie = "_DropboxState"; + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + private const string TokenEndpoint = "https://api.dropbox.com/1/oauth2/token"; + private const string UserInfoEndpoint = "https://api.dropbox.com/1/account/info"; + + private readonly ILogger logger; + private readonly HttpClient httpClient; + + public DropboxAuthenticationHandler(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]; + } + + state = Request.Cookies[StateCookie]; + properties = Options.StateDataFormat.Unprotect(state); + if (properties == null) + { + return null; + } + + // OAuth2 10.12 CSRF + if (!ValidateCorrelationId(properties, logger)) + { + return new AuthenticationTicket(null, properties); + } + + // Check for error + if (Request.Query.Get("error") != null) + 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.AppKey)); + body.Add(new KeyValuePair("client_secret", Options.AppSecret)); + + // 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; + + // Get the Dropbox 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 DropboxAuthenticatedContext(Context, user, accessToken); + 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 authorizationEndpoint = + "https://www.dropbox.com/1/oauth2/authorize" + + "?response_type=code" + + "&client_id=" + Uri.EscapeDataString(Options.AppKey) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri); + + var cookieOptions = new CookieOptions + { + HttpOnly = true, + Secure = Request.IsSecure + }; + + Response.StatusCode = 302; + Response.Cookies.Append(StateCookie, Options.StateDataFormat.Protect(properties), cookieOptions); + Response.Headers.Set("Location", 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 DropboxReturnEndpointContext(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/Dropbox/DropboxAuthenticationMiddleware.cs b/Owin.Security.Providers/Dropbox/DropboxAuthenticationMiddleware.cs new file mode 100644 index 0000000..11e022e --- /dev/null +++ b/Owin.Security.Providers/Dropbox/DropboxAuthenticationMiddleware.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.Dropbox +{ + public class DropboxAuthenticationMiddleware : AuthenticationMiddleware + { + private readonly HttpClient httpClient; + private readonly ILogger logger; + + public DropboxAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, + DropboxAuthenticationOptions options) + : base(next, options) + { + if (String.IsNullOrWhiteSpace(Options.AppKey)) + throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + Resources.Exception_OptionMustBeProvided, "AppKey")); + if (String.IsNullOrWhiteSpace(Options.AppSecret)) + throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + Resources.Exception_OptionMustBeProvided, "AppSecret")); + + logger = app.CreateLogger(); + + if (Options.Provider == null) + Options.Provider = new DropboxAuthenticationProvider(); + + if (Options.StateDataFormat == null) + { + IDataProtector dataProtector = app.CreateDataProtector( + typeof (DropboxAuthenticationMiddleware).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 DropboxAuthenticationHandler(httpClient, logger); + } + + private HttpMessageHandler ResolveHttpMessageHandler(DropboxAuthenticationOptions 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/Dropbox/DropboxAuthenticationOptions.cs b/Owin.Security.Providers/Dropbox/DropboxAuthenticationOptions.cs new file mode 100644 index 0000000..3fbb1b4 --- /dev/null +++ b/Owin.Security.Providers/Dropbox/DropboxAuthenticationOptions.cs @@ -0,0 +1,92 @@ +using System; +using System.Net.Http; +using Microsoft.Owin; +using Microsoft.Owin.Security; + +namespace Owin.Security.Providers.Dropbox +{ + public class DropboxAuthenticationOptions : AuthenticationOptions + { + /// + /// Gets or sets the a pinned certificate validator to use to validate the endpoints used + /// in back channel communications belong to Dropbox + /// + /// + /// 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 Dropbox. + /// 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 Dropbox. + /// + /// + /// 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-dropbox". + /// + 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 Dropbox supplied Application Key + /// + public string AppKey { get; set; } + + /// + /// Gets or sets the Dropbox supplied Application Secret + /// + public string AppSecret { get; set; } + + /// + /// Gets or sets the used in the authentication events + /// + public IDropboxAuthenticationProvider 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 DropboxAuthenticationOptions() + : base("Dropbox") + { + Caption = Constants.DefaultAuthenticationType; + CallbackPath = new PathString("/signin-dropbox"); + AuthenticationMode = AuthenticationMode.Passive; + BackchannelTimeout = TimeSpan.FromSeconds(60); + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Dropbox/Provider/DropboxAuthenticatedContext.cs b/Owin.Security.Providers/Dropbox/Provider/DropboxAuthenticatedContext.cs new file mode 100644 index 0000000..a154eba --- /dev/null +++ b/Owin.Security.Providers/Dropbox/Provider/DropboxAuthenticatedContext.cs @@ -0,0 +1,73 @@ +// 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.Dropbox +{ + /// + /// Contains information about the login session as well as the user . + /// + public class DropboxAuthenticatedContext : BaseContext + { + /// + /// Initializes a + /// + /// The OWIN environment + /// The JSON-serialized user + /// Dropbox Access token + public DropboxAuthenticatedContext(IOwinContext context, JObject user, string accessToken) + : base(context) + { + AccessToken = accessToken; + User = user; + + Id = TryGetValue(user, "uid"); + Name = TryGetValue(user, "display_name"); + } + + /// + /// Gets the JSON-serialized user + /// + /// + /// Contains the Dropbox user obtained from the endpoint https://api.dropbox.com/1/account/info + /// + public JObject User { get; private set; } + + /// + /// Gets the Dropbox OAuth access token + /// + public string AccessToken { get; private set; } + + /// + /// Gets the Dropbox 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/Dropbox/Provider/DropboxAuthenticationProvider.cs b/Owin.Security.Providers/Dropbox/Provider/DropboxAuthenticationProvider.cs new file mode 100644 index 0000000..15c7c92 --- /dev/null +++ b/Owin.Security.Providers/Dropbox/Provider/DropboxAuthenticationProvider.cs @@ -0,0 +1,50 @@ +using System; +using System.Threading.Tasks; + +namespace Owin.Security.Providers.Dropbox +{ + /// + /// Default implementation. + /// + public class DropboxAuthenticationProvider : IDropboxAuthenticationProvider + { + /// + /// Initializes a + /// + public DropboxAuthenticationProvider() + { + 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 Dropbox successfully authenticates a user + /// + /// Contains information about the login session as well as the user . + /// A representing the completed operation. + public virtual Task Authenticated(DropboxAuthenticatedContext 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(DropboxReturnEndpointContext context) + { + return OnReturnEndpoint(context); + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Dropbox/Provider/DropboxReturnEndpointContext.cs b/Owin.Security.Providers/Dropbox/Provider/DropboxReturnEndpointContext.cs new file mode 100644 index 0000000..040f327 --- /dev/null +++ b/Owin.Security.Providers/Dropbox/Provider/DropboxReturnEndpointContext.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.Dropbox +{ + /// + /// Provides context information to middleware providers. + /// + public class DropboxReturnEndpointContext : ReturnEndpointContext + { + /// + /// + /// + /// OWIN environment + /// The authentication ticket + public DropboxReturnEndpointContext( + IOwinContext context, + AuthenticationTicket ticket) + : base(context, ticket) + { + } + } +} diff --git a/Owin.Security.Providers/Dropbox/Provider/IDropboxAuthenticationProvider.cs b/Owin.Security.Providers/Dropbox/Provider/IDropboxAuthenticationProvider.cs new file mode 100644 index 0000000..43d8cfb --- /dev/null +++ b/Owin.Security.Providers/Dropbox/Provider/IDropboxAuthenticationProvider.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; + +namespace Owin.Security.Providers.Dropbox +{ + /// + /// Specifies callback methods which the invokes to enable developer control over the authentication process. /> + /// + public interface IDropboxAuthenticationProvider + { + /// + /// Invoked whenever Dropbox successfully authenticates a user + /// + /// Contains information about the login session as well as the user . + /// A representing the completed operation. + Task Authenticated(DropboxAuthenticatedContext 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(DropboxReturnEndpointContext 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 1782424..e891268 100644 --- a/Owin.Security.Providers/Owin.Security.Providers.csproj +++ b/Owin.Security.Providers/Owin.Security.Providers.csproj @@ -76,6 +76,15 @@ + + + + + + + + + diff --git a/Owin.Security.Providers/Owin.Security.Providers.csproj.DotSettings b/Owin.Security.Providers/Owin.Security.Providers.csproj.DotSettings index 1882ca9..577af8a 100644 --- a/Owin.Security.Providers/Owin.Security.Providers.csproj.DotSettings +++ b/Owin.Security.Providers/Owin.Security.Providers.csproj.DotSettings @@ -1,4 +1,6 @@  True + True + True 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 c7fe185977cae8e8fa81f37efd3c2cdb13733bd4..1d4a316590e2197fd34b9d061a7129948d2d1a40 100644 GIT binary patch delta 267 zcmX}jISztQ6h`6qP@HkX0Y`Xu=a(YCIclF(2<5jsjJqk<}GsG|V` yP2|6AbkK!~9{Lzy2n!>OF~Jlw%wfZUf{O(_EV05GJ~r55hX8vVaEvn*+V2m2UtUW9 delta 267 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 delta 40721 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 K(4&1>;r{}&TKd5N diff --git a/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs b/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs index b284847..0178faa 100755 --- a/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs +++ b/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs @@ -5,6 +5,7 @@ using Microsoft.Owin.Security.Cookies; using Owin; using Owin.Security.Providers.ArcGISOnline; using Owin.Security.Providers.Buffer; +using Owin.Security.Providers.Dropbox; using Owin.Security.Providers.GitHub; using Owin.Security.Providers.GooglePlus; using Owin.Security.Providers.GooglePlus.Provider; @@ -129,9 +130,13 @@ namespace OwinOAuthProvidersDemo // clientId: "", // clientSecret: ""); - app.UseWordPressAuthentication( - clientId: "", - clientSecret: ""); + //app.UseWordPressAuthentication( + // clientId: "", + // clientSecret: ""); + + //app.UseDropboxAuthentication( + // appKey: "", + // appSecret: ""); } } } \ No newline at end of file