Merge branch 'master' of https://github.com/laedit/OwinOAuthProviders into laedit-master

This commit is contained in:
Jerrie Pelser
2014-01-24 11:19:40 +07:00
23 changed files with 1277 additions and 11 deletions

View File

@@ -2,8 +2,8 @@
<package >
<metadata>
<id>Owin.Security.Providers</id>
<version>1.1.0</version>
<authors>Jerrie Pelser</authors>
<version>1.2.0</version>
<authors>Jerrie Pelser, Jérémie Bertrand</authors>
<owners>Jerrie Pelser</owners>
<licenseUrl>http://opensource.org/licenses/MIT</licenseUrl>
<projectUrl>https://github.com/owin-middleware/OwinOAuthProviders</projectUrl>
@@ -13,12 +13,14 @@
- LinkedIn
- Yahoo
- GitHub
- OpenID 2.0 providers
- Steam
</description>
<releaseNotes>
Adds support for authenticating against GitHub
Adds support for authenticating against OpenID 2.0 providers and Steam
</releaseNotes>
<copyright>Copyright 2013</copyright>
<tags>owin katana oauth linkedin yahoo github</tags>
<tags>owin katana oauth linkedin yahoo github openid steam</tags>
<dependencies>
<dependency id="Microsoft.Owin.Security" version="2.0.2" />
<dependency id="Newtonsoft.Json" version="5.0.8" />

View File

@@ -0,0 +1,8 @@

namespace Owin.Security.Providers.OpenID
{
internal static class Constants
{
internal const string DefaultAuthenticationType = "OpenID";
}
}

View File

@@ -0,0 +1,150 @@
using Microsoft.Owin;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Owin.Security.Providers.OpenID.Infrastructure
{
internal class Message
{
public Message(IReadableStringCollection parameters, bool strict)
{
Namespaces = new Dictionary<string, Property>(StringComparer.Ordinal);
Properties = new Dictionary<string, Property>(parameters.Count(), StringComparer.Ordinal);
Add(parameters, strict);
}
public Dictionary<string, Property> Namespaces { get; private set; }
public Dictionary<string, Property> Properties { get; private set; }
/// <summary>
/// Adds the openid parameters from querystring or form body into Namespaces and Properties collections.
/// This normalizes the parameter name, by replacing the variable namespace alias with the
/// actual namespace in the collection's key, and will optionally skip any parameters that are
/// not signed if the strict argument is true.
/// </summary>
/// <param name="parameters">The keys and values of the incoming querystring or form body</param>
/// <param name="strict">True if keys that are not signed should be ignored</param>
private void Add(IReadableStringCollection parameters, bool strict)
{
IEnumerable<KeyValuePair<string, string[]>> addingParameters;
// strict is true if keys that are not signed should be strict
if (strict)
{
IList<string> signed = parameters.GetValues("openid.signed");
if (signed == null ||
signed.Count != 1)
{
// nothing is added if the signed parameter is not present
return;
}
// determine the set of keys that are signed, or which may be used without
// signing. ns, mode, signed, and sig each may be used without signing.
var strictKeys = new HashSet<string>(signed[0]
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(value => "openid." + value)
.Concat(new[] { "openid.ns", "openid.mode", "openid.signed", "openid.sig" }));
// the parameters to add are only the parameters what are in this set
addingParameters = parameters.Where(kv => strictKeys.Contains(kv.Key));
}
else
{
// when strict is false all of the incoming parameters are to be added
addingParameters = parameters;
}
// convert the incoming parameter strings into Property objects. the
// Key is the raw key name. The Name starts of being equal to Key with a
// trailing dot appended. The Value is the query or form value, with a comma delimiter
// inserted between multiply occuring values.
Property[] addingProperties = addingParameters.Select(kv => new Property
{
Key = kv.Key,
Name = kv.Key + ".",
Value = string.Join(",", kv.Value)
}).ToArray();
// first, recognize which parameters are namespace declarations
var namespacePrefixes = new Dictionary<string, Property>(StringComparer.Ordinal);
foreach (var item in addingProperties)
{
// namespaces appear as with "openid.ns" or "openid.ns.alias"
if (item.Name.StartsWith("openid.ns.", StringComparison.Ordinal))
{
// the value of the parameter is the uri of the namespace
item.Namespace = item.Value;
item.Name = "openid." + item.Name.Substring("openid.ns.".Length);
// the namespaces collection is keyed by the ns uri
Namespaces.Add(item.Namespace, item);
// and the prefixes collection is keyed by "openid.alias."
namespacePrefixes.Add(item.Name, item);
}
}
// second, recognize which parameters are property values
foreach (var item in addingProperties)
{
// anything with a namespace was already added to Namespaces
if (item.Namespace == null)
{
// look for the namespace match for this property.
Property match = null;
// try finding where openid.alias.arg2 matches openid.ns.alies namespace
if (item.Name.StartsWith("openid.", StringComparison.Ordinal))
{
int dotIndex = item.Name.IndexOf('.', "openid.".Length);
if (dotIndex != -1)
{
string namespacePrefix = item.Name.Substring(0, dotIndex + 1);
namespacePrefixes.TryGetValue(namespacePrefix, out match);
}
}
// then try finding where openid.arg1 should match openid.ns namespace
if (match == null)
{
namespacePrefixes.TryGetValue("openid.", out match);
}
// when a namespace is found
if (match != null)
{
// the property's namespace is defined, and the namespace's prefix is removed
item.Namespace = match.Namespace;
item.Name = item.Name.Substring(match.Name.Length);
}
// the resulting property key is keyed by the local name and namespace
// so "openid.arg1" becomes "arg1.namespace-uri-of-openid"
// and "openid.alias.arg2" becomes "arg2.namespace-uri-of-alias"
Properties.Add(item.Name + item.Namespace, item);
}
}
}
public bool TryGetValue(string key, out string value)
{
Property property;
if (Properties.TryGetValue(key, out property))
{
value = property.Value;
return true;
}
value = null;
return false;
}
public IEnumerable<KeyValuePair<string, string>> ToFormValues()
{
return Namespaces.Concat(Properties).Select(pair => new KeyValuePair<string, string>(pair.Value.Key, pair.Value.Value));
}
}
}

View File

@@ -0,0 +1,11 @@

namespace Owin.Security.Providers.OpenID.Infrastructure
{
internal class Property
{
public string Key { get; set; }
public string Namespace { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
}

View File

@@ -0,0 +1,50 @@
using Microsoft.Owin;
using System;
namespace Owin.Security.Providers.OpenID
{
/// <summary>
/// Extension methods for using <see cref="OpenIDAuthenticationMiddleware"/>
/// </summary>
public static class OpenIDAuthenticationExtensions
{
/// <summary>
/// Authenticate users using an OpenID provider
/// </summary>
/// <param name="app">The <see cref="IAppBuilder"/> passed to the configuration method</param>
/// <param name="options">Middleware configuration options</param>
/// <returns>The updated <see cref="IAppBuilder"/></returns>
public static IAppBuilder UseOpenIDAuthentication(this IAppBuilder app, OpenIDAuthenticationOptions options)
{
if (app == null)
{
throw new ArgumentNullException("app");
}
if (options == null)
{
throw new ArgumentNullException("options");
}
app.Use(typeof(OpenIDAuthenticationMiddleware), app, options);
return app;
}
/// <summary>
/// Authenticate users using an OpenID provider
/// </summary>
/// <param name="app">The <see cref="IAppBuilder"/> passed to the configuration method</param>
/// <param name="providerUri">The uri of the OpenID provider</param>
/// <param name="providerName">Name of the OpenID provider</param>
/// <returns>The updated <see cref="IAppBuilder"/></returns>
public static IAppBuilder UseOpenIDAuthentication(this IAppBuilder app, string providerUri, string providerName)
{
return UseOpenIDAuthentication(app, new OpenIDAuthenticationOptions
{
ProviderDiscoveryUri = providerUri,
Caption = providerName,
AuthenticationType = providerName,
CallbackPath = new PathString("/signin-openid" + providerName.ToLowerInvariant())
});
}
}
}

View File

@@ -0,0 +1,536 @@
using Microsoft.Owin;
using Microsoft.Owin.Infrastructure;
using Microsoft.Owin.Logging;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Infrastructure;
using Owin.Security.Providers.OpenID.Infrastructure;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
namespace Owin.Security.Providers.OpenID
{
internal class OpenIDAuthenticationHandler : OpenIDAuthenticationHandlerBase<OpenIDAuthenticationOptions>
{
public OpenIDAuthenticationHandler(HttpClient httpClient, ILogger logger)
: base(httpClient, logger)
{ }
}
internal abstract class OpenIDAuthenticationHandlerBase<T> : AuthenticationHandler<T> where T : OpenIDAuthenticationOptions
{
private const string CONTENTTYPE_XRDS = "application/xrds+xml";
private const string CONTENTTYPE_HTML = "text/html";
private const string CONTENTTYPE_XHTML = "application/xhtml+xml";
private const string CONTENTTYPE_XML = "text/xml";
private const string XRDS_LOCATIONHEADER = "X-XRDS-Location";
private const string XRD_NAMESPACE = "xri://$xrd*($v*2.0)";
protected readonly ILogger _logger;
protected readonly HttpClient _httpClient;
public OpenIDAuthenticationHandlerBase(HttpClient httpClient, ILogger logger)
{
_httpClient = httpClient;
_logger = logger;
}
public override async Task<bool> InvokeAsync()
{
if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path)
{
return await InvokeReturnPathAsync();
}
return false;
}
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
{
AuthenticationProperties properties = null;
try
{
IReadableStringCollection query = Request.Query;
properties = UnpackStateParameter(query);
if (properties == null)
{
_logger.WriteWarning("Invalid return state");
return null;
}
// Anti-CSRF
if (!ValidateCorrelationId(properties, _logger))
{
return new AuthenticationTicket(null, properties);
}
Message message = await ParseRequestMessageAsync(query);
bool messageValidated = false;
Property mode;
if (!message.Properties.TryGetValue("mode.http://specs.openid.net/auth/2.0", out mode))
{
_logger.WriteWarning("Missing mode parameter");
return new AuthenticationTicket(null, properties);
}
if (string.Equals("cancel", mode.Value, StringComparison.Ordinal))
{
_logger.WriteWarning("User cancelled signin request");
return new AuthenticationTicket(null, properties);
}
if (string.Equals("id_res", mode.Value, StringComparison.Ordinal))
{
mode.Value = "check_authentication";
var requestBody = new FormUrlEncodedContent(message.ToFormValues());
HttpResponseMessage response = await _httpClient.PostAsync(Options.ProviderLoginUri, requestBody, Request.CallCancelled);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
var verifyBody = new Dictionary<string, string[]>();
foreach (var line in responseBody.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries))
{
int delimiter = line.IndexOf(':');
if (delimiter != -1)
{
verifyBody.Add("openid." + line.Substring(0, delimiter), new[] { line.Substring(delimiter + 1) });
}
}
var verifyMessage = new Message(new ReadableStringCollection(verifyBody), strict: false);
Property isValid;
if (verifyMessage.Properties.TryGetValue("is_valid.http://specs.openid.net/auth/2.0", out isValid))
{
if (string.Equals("true", isValid.Value, StringComparison.Ordinal))
{
messageValidated = true;
}
else
{
messageValidated = false;
}
}
}
// http://openid.net/specs/openid-authentication-2_0.html#verify_return_to
// To verify that the "openid.return_to" URL matches the URL that is processing this assertion:
// * The URL scheme, authority, and path MUST be the same between the two URLs.
// * Any query parameters that are present in the "openid.return_to" URL MUST also
// be present with the same values in the URL of the HTTP request the RP received.
if (messageValidated)
{
// locate the required return_to parameter
string actualReturnTo;
if (!message.TryGetValue("return_to.http://specs.openid.net/auth/2.0", out actualReturnTo))
{
_logger.WriteWarning("openid.return_to parameter missing at return address");
messageValidated = false;
}
else
{
// create the expected return_to parameter based on the URL that is processing
// the assertion, plus exactly and only the the query string parameter (state)
// that this RP must have received
string expectedReturnTo = BuildReturnTo(GetStateParameter(query));
if (!string.Equals(actualReturnTo, expectedReturnTo, StringComparison.Ordinal))
{
_logger.WriteWarning("openid.return_to parameter not equal to expected value based on return address");
messageValidated = false;
}
}
}
if (messageValidated)
{
IDictionary<string, string> attributeExchangeProperties = new Dictionary<string, string>();
foreach (var typeProperty in message.Properties.Values)
{
if (typeProperty.Namespace == "http://openid.net/srv/ax/1.0" &&
typeProperty.Name.StartsWith("type."))
{
string qname = "value." + typeProperty.Name.Substring("type.".Length) + "http://openid.net/srv/ax/1.0";
Property valueProperty;
if (message.Properties.TryGetValue(qname, out valueProperty))
{
attributeExchangeProperties.Add(typeProperty.Value, valueProperty.Value);
}
}
}
var responseNamespaces = new object[]
{
new XAttribute(XNamespace.Xmlns + "openid", "http://specs.openid.net/auth/2.0"),
new XAttribute(XNamespace.Xmlns + "openid.ax", "http://openid.net/srv/ax/1.0")
};
IEnumerable<object> responseProperties = message.Properties
.Where(p => p.Value.Namespace != null)
.Select(p => (object)new XElement(XName.Get(p.Value.Name.Substring(0, p.Value.Name.Length - 1), p.Value.Namespace), p.Value.Value));
var responseMessage = new XElement("response", responseNamespaces.Concat(responseProperties).ToArray());
var identity = new ClaimsIdentity(Options.AuthenticationType);
XElement claimedId = responseMessage.Element(XName.Get("claimed_id", "http://specs.openid.net/auth/2.0"));
if (claimedId != null)
{
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, claimedId.Value, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType));
}
SetIdentityInformations(identity, claimedId.Value, attributeExchangeProperties);
var context = new OpenIDAuthenticatedContext(
Context,
identity,
properties,
responseMessage,
attributeExchangeProperties);
await Options.Provider.Authenticated(context);
return new AuthenticationTicket(context.Identity, context.Properties);
}
return new AuthenticationTicket(null, properties);
}
catch (Exception ex)
{
_logger.WriteError("Authentication failed", ex);
return new AuthenticationTicket(null, properties);
}
}
protected virtual void SetIdentityInformations(ClaimsIdentity identity, string claimedID, IDictionary<string, string> attributeExchangeProperties)
{
string emailValue;
if (attributeExchangeProperties.TryGetValue("http://axschema.org/contact/email", out emailValue))
{
identity.AddClaim(new Claim(ClaimTypes.Email, emailValue, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType));
}
string firstValue;
if (attributeExchangeProperties.TryGetValue("http://axschema.org/namePerson/first", out firstValue))
{
identity.AddClaim(new Claim(ClaimTypes.GivenName, firstValue, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType));
}
string lastValue;
if (attributeExchangeProperties.TryGetValue("http://axschema.org/namePerson/last", out lastValue))
{
identity.AddClaim(new Claim(ClaimTypes.Surname, lastValue, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType));
}
string nameValue;
if (!attributeExchangeProperties.TryGetValue("http://axschema.org/namePerson", out nameValue))
{
if (!string.IsNullOrEmpty(firstValue) && !string.IsNullOrEmpty(lastValue))
{
nameValue = firstValue + " " + lastValue;
}
else if (!string.IsNullOrEmpty(firstValue))
{
nameValue = firstValue;
}
else if (!string.IsNullOrEmpty(lastValue))
{
nameValue = lastValue;
}
else
{
nameValue = emailValue.Substring(0, emailValue.IndexOf('@'));
}
}
identity.AddClaim(new Claim(ClaimTypes.Name, nameValue, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType));
}
private static string GetStateParameter(IReadableStringCollection query)
{
IList<string> values = query.GetValues("state");
if (values != null && values.Count == 1)
{
return values[0];
}
return null;
}
private AuthenticationProperties UnpackStateParameter(IReadableStringCollection query)
{
string state = GetStateParameter(query);
if (state != null)
{
return Options.StateDataFormat.Unprotect(state);
}
return null;
}
private string BuildReturnTo(string state)
{
return Request.Scheme + "://" + Request.Host +
RequestPathBase + Options.CallbackPath +
"?state=" + Uri.EscapeDataString(state);
}
private async Task<Message> ParseRequestMessageAsync(IReadableStringCollection query)
{
if (Request.Method == "POST")
{
IFormCollection form = await Request.ReadFormAsync();
return new Message(form, strict: true);
}
return new Message(query, strict: true);
}
protected override Task ApplyResponseChallengeAsync()
{
if (Response.StatusCode != 401)
{
return Task.FromResult<object>(null);
}
AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
if (challenge != null)
{
DoYadisDiscovery();
if (!string.IsNullOrEmpty(Options.ProviderLoginUri))
{
string requestPrefix = Request.Scheme + Uri.SchemeDelimiter + Request.Host;
var state = challenge.Properties;
if (String.IsNullOrEmpty(state.RedirectUri))
{
state.RedirectUri = requestPrefix + Request.PathBase + Request.Path + Request.QueryString;
}
// Anti-CSRF
GenerateCorrelationId(state);
string returnTo = BuildReturnTo(Options.StateDataFormat.Protect(state));
string authorizationEndpoint =
Options.ProviderLoginUri +
"?openid.ns=" + Uri.EscapeDataString("http://specs.openid.net/auth/2.0") +
"&openid.mode=" + Uri.EscapeDataString("checkid_setup") +
"&openid.claimed_id=" + Uri.EscapeDataString("http://specs.openid.net/auth/2.0/identifier_select") +
"&openid.identity=" + Uri.EscapeDataString("http://specs.openid.net/auth/2.0/identifier_select") +
"&openid.return_to=" + Uri.EscapeDataString(returnTo) +
"&openid.realm=" + Uri.EscapeDataString(requestPrefix) +
"&openid.ns.ax=" + Uri.EscapeDataString("http://openid.net/srv/ax/1.0") +
"&openid.ax.mode=" + Uri.EscapeDataString("fetch_request") +
"&openid.ax.type.email=" + Uri.EscapeDataString("http://axschema.org/contact/email") +
"&openid.ax.type.name=" + Uri.EscapeDataString("http://axschema.org/namePerson") +
"&openid.ax.type.first=" + Uri.EscapeDataString("http://axschema.org/namePerson/first") +
"&openid.ax.type.last=" + Uri.EscapeDataString("http://axschema.org/namePerson/last") +
"&openid.ax.type.email2=" + Uri.EscapeDataString("http://schema.openid.net/contact/email") +
"&openid.ax.type.name2=" + Uri.EscapeDataString("http://schema.openid.net/namePerson") +
"&openid.ax.type.first2=" + Uri.EscapeDataString("http://schema.openid.net/namePerson/first") +
"&openid.ax.type.last2=" + Uri.EscapeDataString("http://schema.openid.net/namePerson/last") +
"&openid.ax.required=" + Uri.EscapeDataString("email,name,first,last,email2,name2,first2,last2");
Response.StatusCode = 302;
Response.Headers.Set("Location", authorizationEndpoint);
}
}
return Task.FromResult<object>(null);
}
private void DoYadisDiscovery()
{
// 1° request
HttpResponseMessage httpResponse = SendRequest(Options.ProviderDiscoveryUri, CONTENTTYPE_XRDS, CONTENTTYPE_HTML, CONTENTTYPE_XHTML);
if (httpResponse.StatusCode != HttpStatusCode.OK)
{
_logger.WriteError(string.Format("HTTP error {0} ({1}) while performing discovery on {2}.", (int)httpResponse.StatusCode, httpResponse.StatusCode, Options.ProviderDiscoveryUri));
return;
}
httpResponse.Content.LoadIntoBufferAsync().Wait();
// 2° request (if necessary)
if (!IsXrdsDocument(httpResponse))
{
IEnumerable<string> uriStrings;
string uriString = null;
if (httpResponse.Headers.TryGetValues(XRDS_LOCATIONHEADER, out uriStrings))
{
uriString = uriStrings.FirstOrDefault();
}
Uri url = null;
if (uriString != null)
{
Uri.TryCreate(uriString, UriKind.Absolute, out url);
}
var contentType = httpResponse.Content.Headers.ContentType;
if (url == null && contentType != null && (contentType.MediaType == CONTENTTYPE_HTML || contentType.MediaType == CONTENTTYPE_XHTML))
{
var readAsString = httpResponse.Content.ReadAsStringAsync();
readAsString.Wait();
url = FindYadisDocumentLocationInHtmlMetaTags(readAsString.Result);
}
if (url == null)
{
_logger.WriteError(string.Format("The uri {0} doesn't return an XRDS document.", Options.ProviderDiscoveryUri));
return;
}
else
{
if (string.Equals(url.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
{
httpResponse = SendRequest(url.AbsoluteUri, CONTENTTYPE_XRDS);
if (httpResponse.StatusCode != HttpStatusCode.OK)
{
_logger.WriteError(string.Format("HTTP error {0} {1} while performing discovery on {2}.", (int)httpResponse.StatusCode, httpResponse.StatusCode, url.AbsoluteUri));
return;
}
if (!IsXrdsDocument(httpResponse))
{
_logger.WriteError(string.Format("The uri {0} doesn't return an XRDS document.", url.AbsoluteUri));
return;
}
}
}
}
// Get the XRDS document
var readAsStringXrdsDoc = httpResponse.Content.ReadAsStringAsync();
readAsStringXrdsDoc.Wait();
// Get provider url from XRDS document
XDocument xrdsDoc = XDocument.Parse(readAsStringXrdsDoc.Result);
Options.ProviderLoginUri = xrdsDoc.Root.Element(XName.Get("XRD", "xri://$xrd*($v*2.0)"))
.Descendants(XName.Get("Service", "xri://$xrd*($v*2.0)"))
.Where(service => service.Descendants(XName.Get("Type", "xri://$xrd*($v*2.0)")).Any(type => type.Value == "http://specs.openid.net/auth/2.0/server"))
.OrderBy(service => service.Attribute("priority").Value)
.Select(service => service.Element(XName.Get("URI", "xri://$xrd*($v*2.0)")).Value)
.FirstOrDefault();
}
// FIXME use an HTTP parser
private static readonly Regex MetaTagXRDSLocationRegex = new Regex(@"<meta http-equiv=""X-XRDS-Location"" content=""(.*?)"">", RegexOptions.Compiled);
private static Uri FindYadisDocumentLocationInHtmlMetaTags(string html)
{
var match = MetaTagXRDSLocationRegex.Match(html);
if (match.Success)
{
Uri uri;
if (Uri.TryCreate(match.Groups[1].Value, UriKind.Absolute, out uri))
{
return uri;
}
}
return null;
}
private HttpResponseMessage SendRequest(string uri, params string[] acceptTypes)
{
HttpRequestMessage httprequest = new HttpRequestMessage(HttpMethod.Get, uri);
if (acceptTypes != null)
{
foreach (string acceptType in acceptTypes)
{
httprequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(acceptType));
}
}
var sendRequest = _httpClient.SendAsync(httprequest);
sendRequest.Wait();
return sendRequest.Result;
}
private static bool IsXrdsDocument(HttpResponseMessage response)
{
if (response.Content.Headers.ContentType == null)
{
return false;
}
if (response.Content.Headers.ContentType.MediaType == CONTENTTYPE_XRDS)
{
return true;
}
if (response.Content.Headers.ContentType.MediaType == CONTENTTYPE_XML)
{
var readAsStream = response.Content.ReadAsStreamAsync();
readAsStream.Wait();
using (var responseStream = readAsStream.Result)
{
XmlReader reader = XmlReader.Create(responseStream, new XmlReaderSettings { MaxCharactersFromEntities = 1024, XmlResolver = null, DtdProcessing = DtdProcessing.Prohibit });
var read = reader.ReadAsync();
read.Wait();
while (read.Result && reader.NodeType != XmlNodeType.Element)
{ }
if (reader.NamespaceURI == XRD_NAMESPACE && reader.Name == "XRDS")
{
return true;
}
}
}
return false;
}
public async Task<bool> InvokeReturnPathAsync()
{
AuthenticationTicket model = await AuthenticateAsync();
if (model == null)
{
_logger.WriteWarning("Invalid return state, unable to redirect.");
Response.StatusCode = 500;
return true;
}
var context = new OpenIDReturnEndpointContext(Context, model);
context.SignInAsAuthenticationType = Options.SignInAsAuthenticationType;
context.RedirectUri = model.Properties.RedirectUri;
model.Properties.RedirectUri = null;
await Options.Provider.ReturnEndpoint(context);
if (context.SignInAsAuthenticationType != null && context.Identity != null)
{
ClaimsIdentity signInIdentity = context.Identity;
if (!string.Equals(signInIdentity.AuthenticationType, context.SignInAsAuthenticationType, StringComparison.Ordinal))
{
signInIdentity = new ClaimsIdentity(signInIdentity.Claims, context.SignInAsAuthenticationType, signInIdentity.NameClaimType, signInIdentity.RoleClaimType);
}
Context.Authentication.SignIn(context.Properties, signInIdentity);
}
if (!context.IsRequestCompleted && context.RedirectUri != null)
{
if (context.Identity == null)
{
// add a redirect hint that sign-in failed in some way
context.RedirectUri = WebUtilities.AddQueryString(context.RedirectUri, "error", "access_denied");
}
Response.Redirect(context.RedirectUri);
context.RequestCompleted();
}
return context.IsRequestCompleted;
}
}
}

View File

@@ -0,0 +1,116 @@
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;
using System;
using System.Globalization;
using System.Net.Http;
namespace Owin.Security.Providers.OpenID
{
/// <summary>
/// OWIN middleware for authenticating users using an OpenID provider
/// </summary>
public class OpenIDAuthenticationMiddleware : OpenIDAuthenticationMiddlewareBase<OpenIDAuthenticationOptions>
{
/// <summary>
/// Initializes a <see cref="OpenIDAuthenticationMiddleware"/>
/// </summary>
/// <param name="next">The next middleware in the OWIN pipeline to invoke</param>
/// <param name="app">The OWIN application</param>
/// <param name="options">Configuration options for the middleware</param>
public OpenIDAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, OpenIDAuthenticationOptions options)
: base(next, app, options)
{ }
protected override AuthenticationHandler<OpenIDAuthenticationOptions> CreateSpecificHandler()
{
return new OpenIDAuthenticationHandler(_httpClient, _logger);
}
}
/// <summary>
/// OWIN middleware for authenticating users using an OpenID provider
/// </summary>
public abstract class OpenIDAuthenticationMiddlewareBase<T> : AuthenticationMiddleware<T> where T : OpenIDAuthenticationOptions
{
protected readonly ILogger _logger;
protected readonly HttpClient _httpClient;
/// <summary>
/// Initializes a <see cref="OpenIDAuthenticationMiddlewareBase"/>
/// </summary>
/// <param name="next">The next middleware in the OWIN pipeline to invoke</param>
/// <param name="app">The OWIN application</param>
/// <param name="options">Configuration options for the middleware</param>
public OpenIDAuthenticationMiddlewareBase(OwinMiddleware next, IAppBuilder app, T options)
: base(next, options)
{
if (String.IsNullOrWhiteSpace(Options.ProviderDiscoveryUri) && Options.AuthenticationType != Constants.DefaultAuthenticationType)
{
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ProviderDiscoveryUri"));
}
_logger = app.CreateLogger<OpenIDAuthenticationMiddleware>();
if (Options.Provider == null)
{
Options.Provider = new OpenIDAuthenticationProvider();
}
if (Options.StateDataFormat == null)
{
IDataProtector dataProtecter = app.CreateDataProtector(
typeof(OpenIDAuthenticationMiddleware).FullName,
Options.AuthenticationType, "v1");
Options.StateDataFormat = new PropertiesDataFormat(dataProtecter);
}
if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType))
{
Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType();
}
_httpClient = new HttpClient(ResolveHttpMessageHandler(Options));
_httpClient.Timeout = Options.BackchannelTimeout;
_httpClient.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB
}
/// <summary>
/// Provides the <see cref="AuthenticationHandler"/> object for processing authentication-related requests.
/// </summary>
/// <returns>An <see cref="AuthenticationHandler"/> configured with the <see cref="OpenIDAuthenticationOptions"/> supplied to the constructor.</returns>
protected override AuthenticationHandler<T> CreateHandler()
{
return CreateSpecificHandler();
}
/// <summary>
/// Provides the <see cref="AuthenticationHandler"/> object for processing authentication-related requests.
/// </summary>
/// <returns>An <see cref="AuthenticationHandler"/> configured with the <see cref="OpenIDAuthenticationOptions"/> supplied to the constructor.</returns>
protected abstract AuthenticationHandler<T> CreateSpecificHandler();
private static HttpMessageHandler ResolveHttpMessageHandler(OpenIDAuthenticationOptions 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;
}
}
}

View File

@@ -0,0 +1,92 @@
using Microsoft.Owin;
using Microsoft.Owin.Security;
using System;
using System.Net.Http;
namespace Owin.Security.Providers.OpenID
{
/// <summary>
/// Configuration options for <see cref="OpenIDAuthenticationMiddleware"/>
/// </summary>
public class OpenIDAuthenticationOptions : AuthenticationOptions
{
/// <summary>
/// Gets or sets the a pinned certificate validator to use to validate the endpoints used
/// in back channel communications belong to the OpenID provider.
/// </summary>
/// <value>
/// The pinned certificate validator.
/// </value>
/// <remarks>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.</remarks>
public ICertificateValidator BackchannelCertificateValidator { get; set; }
/// <summary>
/// Gets or sets timeout value in milliseconds for back channel communications with the OpenID provider.
/// </summary>
/// <value>
/// The back channel timeout.
/// </value>
public TimeSpan BackchannelTimeout { get; set; }
/// <summary>
/// The HttpMessageHandler used to communicate with the OpenID 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>
/// Get or sets the text that the user can display on a sign in user interface.
/// </summary>
public string Caption
{
get { return Description.Caption; }
set { Description.Caption = 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-openid".
/// </summary>
public PathString CallbackPath { get; set; }
/// <summary>
/// Gets or sets the name of another authentication middleware which will be responsible for actually issuing a user <see cref="System.Security.Claims.ClaimsIdentity"/>.
/// </summary>
public string SignInAsAuthenticationType { get; set; }
/// <summary>
/// Gets or sets the <see cref="IOpenIDAuthenticationProvider"/> used to handle authentication events.
/// </summary>
public IOpenIDAuthenticationProvider Provider { get; set; }
/// <summary>
/// Gets or sets the type used to secure data handled by the middleware.
/// </summary>
public ISecureDataFormat<AuthenticationProperties> StateDataFormat { get; set; }
/// <summary>
/// The OpenID provider discovery uri
/// </summary>
public string ProviderDiscoveryUri { get; set; }
/// <summary>
/// The OpenID provider login uri
/// </summary>
internal string ProviderLoginUri { get; set; }
/// <summary>
/// Initializes a new <see cref="OpenIDAuthenticationOptions"/>
/// </summary>
public OpenIDAuthenticationOptions()
: base(Constants.DefaultAuthenticationType)
{
Caption = Constants.DefaultAuthenticationType;
CallbackPath = new PathString("/signin-openid");
AuthenticationMode = AuthenticationMode.Passive;
BackchannelTimeout = TimeSpan.FromSeconds(60);
}
}
}

View File

@@ -0,0 +1,24 @@
using System.Threading.Tasks;
namespace Owin.Security.Providers.OpenID
{
/// <summary>
/// Specifies callback methods which the <see cref="OpenIDAuthenticationMiddleware"></see> invokes to enable developer control over the authentication process. />
/// </summary>
public interface IOpenIDAuthenticationProvider
{
/// <summary>
/// Invoked whenever OpenID succesfully authenticates a user
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
Task Authenticated(OpenIDAuthenticatedContext 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">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
Task ReturnEndpoint(OpenIDReturnEndpointContext context);
}
}

View File

@@ -0,0 +1,51 @@
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Provider;
using System.Collections.Generic;
using System.Security.Claims;
using System.Xml.Linq;
namespace Owin.Security.Providers.OpenID
{
/// <summary>
/// Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.
/// </summary>
public class OpenIDAuthenticatedContext : BaseContext
{
/// <summary>
/// Initializes a <see cref="OpenIDAuthenticatedContext"/>
/// </summary>
/// <param name="context">The OWIN environment</param>
/// <param name="identity">The <see cref="ClaimsIdentity"/> representing the user</param>
/// <param name="properties">A property bag for common authentication properties</param>
/// <param name="responseMessage"></param>
/// <param name="attributeExchangeProperties"></param>
public OpenIDAuthenticatedContext(
IOwinContext context,
ClaimsIdentity identity,
AuthenticationProperties properties,
XElement responseMessage,
IDictionary<string, string> attributeExchangeProperties)
: base(context)
{
Identity = identity;
Properties = properties;
ResponseMessage = responseMessage;
AttributeExchangeProperties = attributeExchangeProperties;
}
/// <summary>
/// Gets or sets the <see cref="ClaimsIdentity"/> representing the user
/// </summary>
public ClaimsIdentity Identity { get; set; }
/// <summary>
/// Gets or sets a property bag for common authentication properties
/// </summary>
public AuthenticationProperties Properties { get; set; }
public XElement ResponseMessage { get; set; }
public IDictionary<string, string> AttributeExchangeProperties { get; private set; }
}
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Threading.Tasks;
namespace Owin.Security.Providers.OpenID
{
/// <summary>
/// Default <see cref="IOpenIDAuthenticationProvider"/> implementation.
/// </summary>
public class OpenIDAuthenticationProvider : IOpenIDAuthenticationProvider
{
/// <summary>
/// Initializes a <see cref="OpenIDAuthenticationProvider"/>
/// </summary>
public OpenIDAuthenticationProvider()
{
OnAuthenticated = context => Task.FromResult<object>(null);
OnReturnEndpoint = context => Task.FromResult<object>(null);
}
/// <summary>
/// Gets or sets the function that is invoked when the Authenticated method is invoked.
/// </summary>
public Func<OpenIDAuthenticatedContext, Task> OnAuthenticated { get; set; }
/// <summary>
/// Gets or sets the function that is invoked when the ReturnEndpoint method is invoked.
/// </summary>
public Func<OpenIDReturnEndpointContext, Task> OnReturnEndpoint { get; set; }
/// <summary>
/// Invoked whenever OpenID succesfully authenticates a user
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
public virtual Task Authenticated(OpenIDAuthenticatedContext context)
{
return OnAuthenticated(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">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
public virtual Task ReturnEndpoint(OpenIDReturnEndpointContext context)
{
return OnReturnEndpoint(context);
}
}
}

View File

@@ -0,0 +1,21 @@
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Provider;
namespace Owin.Security.Providers.OpenID
{
/// <summary>
/// Provides context information to middleware providers.
/// </summary>
public class OpenIDReturnEndpointContext : ReturnEndpointContext
{
/// <summary>
/// Initializes a <see cref="OpenIDReturnEndpointContext"/>
/// </summary>
/// <param name="context">OWIN environment</param>
/// <param name="ticket">The authentication ticket</param>
public OpenIDReturnEndpointContext(IOwinContext context, AuthenticationTicket ticket)
: base(context, ticket)
{ }
}
}

View File

@@ -73,12 +73,27 @@
<Compile Include="LinkedIn\Provider\LinkedInAuthenticationProvider.cs" />
<Compile Include="LinkedIn\Provider\LinkedInReturnEndpointContext.cs" />
<Compile Include="LinkedIn\Provider\ILinkedInAuthenticationProvider.cs" />
<Compile Include="OpenID\Constants.cs" />
<Compile Include="OpenID\Infrastructure\Message.cs" />
<Compile Include="OpenID\Infrastructure\Property.cs" />
<Compile Include="OpenID\OpenIDAuthenticationExtensions.cs" />
<Compile Include="OpenID\OpenIDAuthenticationHandler.cs" />
<Compile Include="OpenID\OpenIDAuthenticationMiddleware.cs" />
<Compile Include="OpenID\OpenIDAuthenticationOptions.cs" />
<Compile Include="OpenID\Provider\IOpenIDAuthenticationProvider.cs" />
<Compile Include="OpenID\Provider\OpenIDAuthenticatedContext.cs" />
<Compile Include="OpenID\Provider\OpenIDAuthenticationProvider.cs" />
<Compile Include="OpenID\Provider\OpenIDReturnEndpointContext.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Steam\SteamAuthenticationExtensions.cs" />
<Compile Include="Steam\SteamAuthenticationHandler.cs" />
<Compile Include="Steam\SteamAuthenticationMiddleware.cs" />
<Compile Include="Steam\SteamAuthenticationOptions.cs" />
<Compile Include="Yahoo\Constants.cs" />
<Compile Include="Yahoo\Messages\AccessToken.cs" />
<Compile Include="Yahoo\Messages\RequestToken.cs" />
@@ -102,6 +117,7 @@
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View File

@@ -0,0 +1,50 @@
using Microsoft.Owin;
using System;
namespace Owin.Security.Providers.Steam
{
/// <summary>
/// Extension methods for using <see cref="SteamAuthenticationMiddleware"/>
/// </summary>
public static class SteamAuthenticationExtensions
{
/// <summary>
/// Authenticate users using Steam
/// </summary>
/// <param name="app">The <see cref="IAppBuilder"/> passed to the configuration method</param>
/// <param name="options">Middleware configuration options</param>
/// <returns>The updated <see cref="IAppBuilder"/></returns>
public static IAppBuilder UseSteamAuthentication(this IAppBuilder app, SteamAuthenticationOptions options)
{
if (app == null)
{
throw new ArgumentNullException("app");
}
if (options == null)
{
throw new ArgumentNullException("options");
}
app.Use(typeof(SteamAuthenticationMiddleware), app, options);
return app;
}
/// <summary>
/// Authenticate users using Steam
/// </summary>
/// <param name="app">The <see cref="IAppBuilder"/> passed to the configuration method</param>
/// <param name="applicationKey">The steam application key</param>
/// <returns>The updated <see cref="IAppBuilder"/></returns>
public static IAppBuilder UseSteamAuthentication(this IAppBuilder app, string applicationKey)
{
return UseSteamAuthentication(app, new SteamAuthenticationOptions
{
ProviderDiscoveryUri = "http://steamcommunity.com/openid/",
Caption = "Steam",
AuthenticationType = "Steam",
CallbackPath = new PathString("/signin-openidsteam"),
ApplicationKey = applicationKey
});
}
}
}

View File

@@ -0,0 +1,36 @@
using Microsoft.Owin.Logging;
using Newtonsoft.Json;
using Owin.Security.Providers.OpenID;
using System.Collections.Generic;
using System.Net.Http;
using System.Security.Claims;
using System.Text.RegularExpressions;
namespace Owin.Security.Providers.Steam
{
internal sealed class SteamAuthenticationHandler : OpenIDAuthenticationHandlerBase<SteamAuthenticationOptions>
{
private readonly Regex AccountIDRegex = new Regex(@"^http://steamcommunity\.com/openid/id/(7[0-9]{15,25})$", RegexOptions.Compiled);
private const string UserInfoUri = "http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key={0}&steamids={1}";
public SteamAuthenticationHandler(HttpClient httpClient, ILogger logger)
: base(httpClient, logger)
{ }
protected override void SetIdentityInformations(ClaimsIdentity identity, string claimedID, IDictionary<string, string> attributeExchangeProperties)
{
Match accountIDMatch = AccountIDRegex.Match(claimedID);
if (accountIDMatch.Success)
{
string accountID = accountIDMatch.Groups[1].Value;
var getUserInfoTask = _httpClient.GetStringAsync(string.Format(UserInfoUri, Options.ApplicationKey, accountID));
getUserInfoTask.Wait();
string userInfoRaw = getUserInfoTask.Result;
dynamic userInfo = JsonConvert.DeserializeObject<dynamic>(userInfoRaw);
identity.AddClaim(new Claim(ClaimTypes.Name, (string)userInfo.response.players[0].personaname, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType));
}
}
}
}

View File

@@ -0,0 +1,27 @@
using Microsoft.Owin;
using Microsoft.Owin.Security.Infrastructure;
using Owin.Security.Providers.OpenID;
namespace Owin.Security.Providers.Steam
{
/// <summary>
/// OWIN middleware for authenticating users using an OpenID provider
/// </summary>
public sealed class SteamAuthenticationMiddleware : OpenIDAuthenticationMiddlewareBase<SteamAuthenticationOptions>
{
/// <summary>
/// Initializes a <see cref="SteamAuthenticationMiddleware"/>
/// </summary>
/// <param name="next">The next middleware in the OWIN pipeline to invoke</param>
/// <param name="app">The OWIN application</param>
/// <param name="options">Configuration options for the middleware</param>
public SteamAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, SteamAuthenticationOptions options)
: base(next, app, options)
{ }
protected override AuthenticationHandler<SteamAuthenticationOptions> CreateSpecificHandler()
{
return new SteamAuthenticationHandler(_httpClient, _logger);
}
}
}

View File

@@ -0,0 +1,9 @@
using Owin.Security.Providers.OpenID;
namespace Owin.Security.Providers.Steam
{
public sealed class SteamAuthenticationOptions : OpenIDAuthenticationOptions
{
public string ApplicationKey { get; set; }
}
}

View File

@@ -5,6 +5,8 @@ using Owin;
using Owin.Security.Providers.GitHub;
using Owin.Security.Providers.LinkedIn;
using Owin.Security.Providers.Yahoo;
using Owin.Security.Providers.OpenID;
using Owin.Security.Providers.Steam;
namespace OwinOAuthProvidersDemo
{
@@ -44,6 +46,16 @@ namespace OwinOAuthProvidersDemo
// "");
//app.UseGitHubAuthentication("", "");
//app.UseOpenIDAuthentication("http://me.yahoo.com/", "Yahoo");
//app.UseOpenIDAuthentication("https://openid.stackexchange.com/", "StackExchange");
//app.UseOpenIDAuthentication("https://www.google.com/accounts/o8/id", "Google");
//app.UseSteamAuthentication(applicationKey: "");
}
}
}

View File

@@ -7,8 +7,10 @@
if (loginProviders.Count() == 0)
{
<div>
<p>There are no external authentication services configured. See <a href="http://go.microsoft.com/fwlink/?LinkId=313242">this article</a>
for details on setting up this ASP.NET application to support logging in via external services.</p>
<p>
There are no external authentication services configured. See <a href="http://go.microsoft.com/fwlink/?LinkId=313242">this article</a>
for details on setting up this ASP.NET application to support logging in via external services.
</p>
</div>
}
else
@@ -20,10 +22,10 @@
@Html.AntiForgeryToken()
<div id="socialLoginList">
<p>
@foreach (AuthenticationDescription p in loginProviders)
{
<button type="submit" class="btn btn-default" id="@p.AuthenticationType" name="provider" value="@p.AuthenticationType" title="Log in using your @p.Caption account">@p.AuthenticationType</button>
}
@foreach (AuthenticationDescription p in loginProviders)
{
<button type="submit" class="btn btn-default" id="@p.AuthenticationType" name="provider" value="@p.AuthenticationType" title="Log in using your @p.Caption account">@p.AuthenticationType</button>
}
</p>
</div>
}

View File

@@ -9,7 +9,7 @@
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 --></configSections>
<connectionStrings>
<add name="DefaultConnection" connectionString="Data Source=(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\aspnet-OwinOAuthProvidersDemo-20131113093833.mdf;Initial Catalog=aspnet-OwinOAuthProvidersDemo-20131113093833;Integrated Security=True" providerName="System.Data.SqlClient" />
<add name="DefaultConnection" connectionString="Data Source=(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\aspnet-OwinOAuthProvidersDemo-20131113093838.mdf;Initial Catalog=aspnet-OwinOAuthProvidersDemo-20131113093838;Integrated Security=True" providerName="System.Data.SqlClient" />
</connectionStrings>
<appSettings>
<add key="webpages:Version" value="3.0.0.0" />

View File

@@ -3,5 +3,8 @@ OwinOAuthProviders
Authentication providers for OWIN (Katana). Includes OAuth providers for:
- Yahoo
- LinkedIn
- GitHub
- OpenID 2.0 providers
- Steam
For more details on how to use these your can view [this blog post](http://www.beabigrockstar.com/introducing-the-yahoo-linkedin-oauth-security-providers-for-owin)