Merge branch 'master' of https://github.com/laedit/OwinOAuthProviders into laedit-master
This commit is contained in:
@@ -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" />
|
||||
|
||||
8
Owin.Security.Providers/OpenID/Constants.cs
Normal file
8
Owin.Security.Providers/OpenID/Constants.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
namespace Owin.Security.Providers.OpenID
|
||||
{
|
||||
internal static class Constants
|
||||
{
|
||||
internal const string DefaultAuthenticationType = "OpenID";
|
||||
}
|
||||
}
|
||||
150
Owin.Security.Providers/OpenID/Infrastructure/Message.cs
Normal file
150
Owin.Security.Providers/OpenID/Infrastructure/Message.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Owin.Security.Providers/OpenID/Infrastructure/Property.cs
Normal file
11
Owin.Security.Providers/OpenID/Infrastructure/Property.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
536
Owin.Security.Providers/OpenID/OpenIDAuthenticationHandler.cs
Normal file
536
Owin.Security.Providers/OpenID/OpenIDAuthenticationHandler.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
116
Owin.Security.Providers/OpenID/OpenIDAuthenticationMiddleware.cs
Normal file
116
Owin.Security.Providers/OpenID/OpenIDAuthenticationMiddleware.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
36
Owin.Security.Providers/Steam/SteamAuthenticationHandler.cs
Normal file
36
Owin.Security.Providers/Steam/SteamAuthenticationHandler.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Owin.Security.Providers.OpenID;
|
||||
|
||||
namespace Owin.Security.Providers.Steam
|
||||
{
|
||||
public sealed class SteamAuthenticationOptions : OpenIDAuthenticationOptions
|
||||
{
|
||||
public string ApplicationKey { get; set; }
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -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: "");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user