Additions for OpenIdConnectMiddleware and OAuthBearer Beta1.
This commit is contained in:
parent
fbe80ee64e
commit
49e66f0311
|
|
@ -3,5 +3,6 @@
|
|||
<packageSources>
|
||||
<add key="AspNetVNext" value="https://www.myget.org/F/aspnetvnext/api/v2" />
|
||||
<add key="NuGet.org" value="https://nuget.org/api/v2/" />
|
||||
<add key="AzureADNighty" value="http://www.myget.org/F/azureadwebstacknightly"/>
|
||||
</packageSources>
|
||||
</configuration>
|
||||
|
|
|
|||
47
Security.sln
47
Security.sln
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.22013.1
|
||||
VisualStudioVersion = 14.0.22422.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4D2B6A51-2F9F-44F5-8131-EA5CAC053652}"
|
||||
EndProject
|
||||
|
|
@ -36,6 +36,12 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Security.O
|
|||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CookieSessionSample", "samples\CookieSessionSample\CookieSessionSample.kproj", "{19711880-46DA-4A26-9E0F-9B2E41D27651}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "OpenIdConnectSample", "samples\OpenIdConnectSample\OpenIdConnectSample.kproj", "{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Security.OAuthBearer", "src\Microsoft.AspNet.Security.OAuthBearer\Microsoft.AspNet.Security.OAuthBearer.kproj", "{2755BFE5-7421-4A31-A644-F817DF5CAA98}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Security.OpenIdConnect", "src\Microsoft.AspNet.Security.OpenIdConnect\Microsoft.AspNet.Security.OpenIdConnect.kproj", "{674D128E-83BB-481A-A9D9-6D47872E1FC8}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -156,6 +162,42 @@ Global
|
|||
{19711880-46DA-4A26-9E0F-9B2E41D27651}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{19711880-46DA-4A26-9E0F-9B2E41D27651}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{19711880-46DA-4A26-9E0F-9B2E41D27651}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Release|x86.Build.0 = Release|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Release|x86.Build.0 = Release|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -172,5 +214,8 @@ Global
|
|||
{1FCF26C2-A3C7-4308-B698-4AFC3560BC0C} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
|
||||
{4A636011-68EE-4CE5-836D-EA8E13CF71E4} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
|
||||
{19711880-46DA-4A26-9E0F-9B2E41D27651} = {F8C0AA27-F3FB-4286-8E4C-47EF86B539FF}
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B} = {F8C0AA27-F3FB-4286-8E4C-47EF86B539FF}
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>bef0f5c3-ef4e-4649-9c49-d5e279a3ca2b</ProjectGuid>
|
||||
<RootNamespace>OpenIDConnectSample</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<AssemblyName>OpenIDConnectSample</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<DevelopmentServerPort>42023</DevelopmentServerPort>
|
||||
<CommandLineArguments />
|
||||
<DebugTarget>
|
||||
</DebugTarget>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<UserProperties project_1json__JSONSchema="http://www.asp.net/media/4878834/project.json" />
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.AspNet.Security.OpenIdConnect;
|
||||
using Microsoft.AspNet.Http.Security;
|
||||
using Microsoft.AspNet.Security;
|
||||
|
||||
namespace OpenIdConnectSample
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseServices(services =>
|
||||
{
|
||||
services.AddDataProtection();
|
||||
services.Configure<ExternalAuthenticationOptions>(options =>
|
||||
{
|
||||
options.SignInAsAuthenticationType = OpenIdConnectAuthenticationDefaults.AuthenticationType;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
app.UseCookieAuthentication(options =>
|
||||
{
|
||||
options.AuthenticationType = OpenIdConnectAuthenticationDefaults.AuthenticationType;
|
||||
});
|
||||
|
||||
app.UseOpenIdConnectAuthentication(options =>
|
||||
{
|
||||
options.ClientId = "fe78e0b4-6fe7-47e6-812c-fb75cee266a4";
|
||||
options.Authority = "https://login.windows.net/cyrano.onmicrosoft.com";
|
||||
options.RedirectUri = "http://localhost:42023";
|
||||
options.SignInAsAuthenticationType = OpenIdConnectAuthenticationDefaults.AuthenticationType;
|
||||
options.AuthenticationType = OpenIdConnectAuthenticationDefaults.AuthenticationType;
|
||||
});
|
||||
|
||||
app.Run(async context =>
|
||||
{
|
||||
if (context.User == null || !context.User.Identity.IsAuthenticated)
|
||||
{
|
||||
context.Response.Challenge(new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
|
||||
|
||||
context.Response.ContentType = "text/plain";
|
||||
await context.Response.WriteAsync("Hello First timer");
|
||||
return;
|
||||
}
|
||||
|
||||
context.Response.ContentType = "text/plain";
|
||||
await context.Response.WriteAsync("Hello Authenticated User");
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"Kestrel": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security.Cookies": "1.0.0-*",
|
||||
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security.OpenIdConnect": "1.0.0-*",
|
||||
"Microsoft.AspNet.Server.WebListener": "1.0.0-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"aspnet50": { },
|
||||
"aspnetcore50": { }
|
||||
},
|
||||
"commands": {
|
||||
"web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:12345",
|
||||
"kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5004"
|
||||
},
|
||||
"webroot": "wwwroot"
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies callback methods which the <see cref="OAuthBearerAuthenticationMiddleware"></see> invokes to enable developer control over the authentication process. />
|
||||
/// </summary>
|
||||
public interface IOAuthBearerAuthenticationNotifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoked before the <see cref="System.Security.Claims.ClaimsIdentity"/> is created. Gives the application an
|
||||
/// opportunity to find the identity from a different location, adjust, or reject the token.
|
||||
/// </summary>
|
||||
/// <param name="context">Contains the token string.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
|
||||
Task RequestToken(OAuthRequestTokenContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Called each time a request identity has been validated by the middleware. By implementing this method the
|
||||
/// application may alter or reject the identity which has arrived with the request.
|
||||
/// </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 ValidateIdentity(OAuthValidateIdentityContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Called each time a challenge is being sent to the client. By implementing this method the application
|
||||
/// may modify the challenge as needed.
|
||||
/// </summary>
|
||||
/// <param name="context">Contains the default challenge.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
|
||||
Task ApplyChallenge(OAuthChallengeContext context);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth bearer token middleware provider
|
||||
/// </summary>
|
||||
public class OAuthBearerAuthenticationNotifications : IOAuthBearerAuthenticationNotifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OAuthBearerAuthenticationProvider"/> class
|
||||
/// </summary>
|
||||
public OAuthBearerAuthenticationNotifications()
|
||||
{
|
||||
OnRequestToken = context => Task.FromResult<object>(null);
|
||||
OnValidateIdentity = context => Task.FromResult<object>(null);
|
||||
OnApplyChallenge = context =>
|
||||
{
|
||||
context.HttpContext.Response.Headers.AppendValues("WWW-Authenticate", context.Challenge);
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles processing OAuth bearer token.
|
||||
/// </summary>
|
||||
public Func<OAuthRequestTokenContext, Task> OnRequestToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Handles validating the identity produced from an OAuth bearer token.
|
||||
/// </summary>
|
||||
public Func<OAuthValidateIdentityContext, Task> OnValidateIdentity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Handles applying the authentication challenge to the response message.
|
||||
/// </summary>
|
||||
public Func<OAuthChallengeContext, Task> OnApplyChallenge { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Handles processing OAuth bearer token.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public virtual Task RequestToken(OAuthRequestTokenContext context)
|
||||
{
|
||||
return OnRequestToken(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles validating the identity produced from an OAuth bearer token.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public virtual Task ValidateIdentity(OAuthValidateIdentityContext context)
|
||||
{
|
||||
return OnValidateIdentity.Invoke(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles applying the authentication challenge to the response message.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public Task ApplyChallenge(OAuthChallengeContext context)
|
||||
{
|
||||
return OnApplyChallenge(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the authentication ticket data from an OAuth bearer token.
|
||||
/// </summary>
|
||||
public class OAuthValidateIdentityContext : BaseValidatingTicketContext<OAuthBearerAuthenticationOptions>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OAuthValidateIdentityContext"/> class
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="ticket"></param>
|
||||
public OAuthValidateIdentityContext(
|
||||
HttpContext context,
|
||||
OAuthBearerAuthenticationOptions options,
|
||||
AuthenticationTicket ticket) : base(context, options, ticket)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Security.DataHandler;
|
||||
using Microsoft.AspNet.Security.DataProtection;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuth
|
||||
{
|
||||
|
|
@ -45,18 +45,22 @@ namespace Microsoft.AspNet.Security.OAuth
|
|||
{
|
||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "AuthenticationType"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Options.ClientId))
|
||||
{
|
||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientId"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Options.ClientSecret))
|
||||
{
|
||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Options.AuthorizationEndpoint))
|
||||
{
|
||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "AuthorizationEndpoint"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Options.TokenEndpoint))
|
||||
{
|
||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "TokenEndpoint"));
|
||||
|
|
|
|||
|
|
@ -1,126 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
using Microsoft.Framework.Logging;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuth
|
||||
{
|
||||
internal class OAuthBearerAuthenticationHandler : AuthenticationHandler<OAuthBearerAuthenticationOptions>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly string _challenge;
|
||||
|
||||
public OAuthBearerAuthenticationHandler(ILogger logger, string challenge)
|
||||
{
|
||||
_logger = logger;
|
||||
_challenge = challenge;
|
||||
}
|
||||
|
||||
protected override AuthenticationTicket AuthenticateCore()
|
||||
{
|
||||
return AuthenticateCoreAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Find token in default location
|
||||
string requestToken = null;
|
||||
string authorization = Request.Headers.Get("Authorization");
|
||||
if (!string.IsNullOrEmpty(authorization))
|
||||
{
|
||||
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
requestToken = authorization.Substring("Bearer ".Length).Trim();
|
||||
}
|
||||
}
|
||||
|
||||
// Give application opportunity to find from a different location, adjust, or reject token
|
||||
var requestTokenContext = new OAuthRequestTokenContext(Context, requestToken);
|
||||
await Options.Notifications.RequestToken(requestTokenContext);
|
||||
|
||||
// If no token found, no further work possible
|
||||
if (string.IsNullOrEmpty(requestTokenContext.Token))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Call provider to process the token into data
|
||||
var tokenReceiveContext = new AuthenticationTokenReceiveContext(
|
||||
Context,
|
||||
Options.AccessTokenFormat,
|
||||
requestTokenContext.Token);
|
||||
|
||||
await Options.AccessTokenProvider.ReceiveAsync(tokenReceiveContext);
|
||||
if (tokenReceiveContext.Ticket == null)
|
||||
{
|
||||
tokenReceiveContext.DeserializeTicket(tokenReceiveContext.Token);
|
||||
}
|
||||
|
||||
AuthenticationTicket ticket = tokenReceiveContext.Ticket;
|
||||
if (ticket == null)
|
||||
{
|
||||
_logger.WriteWarning("invalid bearer token received");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Validate expiration time if present
|
||||
DateTimeOffset currentUtc = Options.SystemClock.UtcNow;
|
||||
|
||||
if (ticket.Properties.ExpiresUtc.HasValue &&
|
||||
ticket.Properties.ExpiresUtc.Value < currentUtc)
|
||||
{
|
||||
_logger.WriteWarning("expired bearer token received");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Give application final opportunity to override results
|
||||
var context = new OAuthValidateIdentityContext(Context, Options, ticket);
|
||||
if (ticket != null &&
|
||||
ticket.Identity != null &&
|
||||
ticket.Identity.IsAuthenticated)
|
||||
{
|
||||
// bearer token with identity starts validated
|
||||
context.Validated();
|
||||
}
|
||||
|
||||
await Options.Notifications.ValidateIdentity(context);
|
||||
if (!context.IsValidated)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// resulting identity values go back to caller
|
||||
return context.Ticket;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.WriteError("Authentication failed", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyResponseChallenge()
|
||||
{
|
||||
if (Response.StatusCode != 401)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ChallengeContext != null)
|
||||
{
|
||||
OAuthChallengeContext challengeContext = new OAuthChallengeContext(Context, _challenge);
|
||||
Options.Notifications.ApplyChallenge(challengeContext);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyResponseGrant()
|
||||
{
|
||||
// N/A
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Security.DataHandler;
|
||||
using Microsoft.AspNet.Security.DataProtection;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// Bearer authentication middleware component which is added to an HTTP pipeline. This class is not
|
||||
/// created by application code directly, instead it is added by calling the the IAppBuilder UseOAuthBearerAuthentication
|
||||
/// extension method.
|
||||
/// </summary>
|
||||
public class OAuthBearerAuthenticationMiddleware : AuthenticationMiddleware<OAuthBearerAuthenticationOptions>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly string _challenge;
|
||||
|
||||
/// <summary>
|
||||
/// Bearer authentication component which is added to an HTTP pipeline. This constructor is not
|
||||
/// called by application code directly, instead it is added by calling the the IAppBuilder UseOAuthBearerAuthentication
|
||||
/// extension method.
|
||||
/// </summary>
|
||||
public OAuthBearerAuthenticationMiddleware(
|
||||
RequestDelegate next,
|
||||
IServiceProvider services,
|
||||
IDataProtectionProvider dataProtectionProvider,
|
||||
ILoggerFactory loggerFactory,
|
||||
IOptions<OAuthBearerAuthenticationOptions> options,
|
||||
ConfigureOptions<OAuthBearerAuthenticationOptions> configureOptions)
|
||||
: base(next, services, options, configureOptions)
|
||||
{
|
||||
_logger = loggerFactory.Create<OAuthBearerAuthenticationMiddleware>();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Options.Challenge))
|
||||
{
|
||||
_challenge = Options.Challenge;
|
||||
}
|
||||
else if (string.IsNullOrWhiteSpace(Options.Realm))
|
||||
{
|
||||
_challenge = "Bearer";
|
||||
}
|
||||
else
|
||||
{
|
||||
_challenge = "Bearer realm=\"" + Options.Realm + "\"";
|
||||
}
|
||||
|
||||
if (Options.Notifications == null)
|
||||
{
|
||||
Options.Notifications = new OAuthBearerAuthenticationNotifications();
|
||||
}
|
||||
|
||||
if (Options.AccessTokenFormat == null)
|
||||
{
|
||||
IDataProtector dataProtector = dataProtectionProvider.CreateDataProtector(
|
||||
this.GetType().FullName, Options.AuthenticationType, "v1");
|
||||
Options.AccessTokenFormat = new TicketDataFormat(dataProtector);
|
||||
}
|
||||
|
||||
if (Options.AccessTokenProvider == null)
|
||||
{
|
||||
Options.AccessTokenProvider = new AuthenticationTokenProvider();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by the AuthenticationMiddleware base class to create a per-request handler.
|
||||
/// </summary>
|
||||
/// <returns>A new instance of the request handler</returns>
|
||||
protected override AuthenticationHandler<OAuthBearerAuthenticationOptions> CreateHandler()
|
||||
{
|
||||
return new OAuthBearerAuthenticationHandler(_logger, _challenge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// Options class provides information needed to control Bearer Authentication middleware behavior
|
||||
/// </summary>
|
||||
public class OAuthBearerAuthenticationOptions : AuthenticationOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an instance of bearer authentication options with default values.
|
||||
/// </summary>
|
||||
public OAuthBearerAuthenticationOptions() : base()
|
||||
{
|
||||
SystemClock = new SystemClock();
|
||||
AuthenticationType = OAuthBearerAuthenticationDefaults.AuthenticationType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines what realm value is included when the bearer middleware adds a response header to an unauthorized request.
|
||||
/// If not assigned, the response header does not have a realm.
|
||||
/// </summary>
|
||||
public string Realm { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the full challenge to send to the client, and should start with "Bearer". If a challenge is provided then the
|
||||
/// Realm property is ignored. If no challenge is specified then one is created using "Bearer" and the value of the Realm
|
||||
/// property.
|
||||
/// </summary>
|
||||
public string Challenge { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The object provided by the application to process events raised by the bearer authentication middleware.
|
||||
/// The application may implement the interface fully, or it may create an instance of OAuthBearerAuthenticationProvider
|
||||
/// and assign delegates only to the events it wants to process.
|
||||
/// </summary>
|
||||
public IOAuthBearerAuthenticationNotifications Notifications { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The data format used to un-protect the information contained in the access token.
|
||||
/// If not provided by the application the default data protection provider depends on the host server.
|
||||
/// The SystemWeb host on IIS will use ASP.NET machine key data protection, and HttpListener and other self-hosted
|
||||
/// servers will use DPAPI data protection. If a different access token
|
||||
/// provider or format is assigned, a compatible instance must be assigned to the OAuthAuthorizationServerOptions.AccessTokenProvider
|
||||
/// and OAuthAuthorizationServerOptions.AccessTokenFormat of the authorization server.
|
||||
/// </summary>
|
||||
public ISecureDataFormat<AuthenticationTicket> AccessTokenFormat { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Receives the bearer token the client application will be providing to web application. If not provided the token
|
||||
/// produced on the server's default data protection by using the AccessTokenFormat. If a different access token
|
||||
/// provider or format is assigned, a compatible instance must be assigned to the OAuthAuthorizationServerOptions.AccessTokenProvider
|
||||
/// and OAuthAuthorizationServerOptions.AccessTokenFormat of the authorization server.
|
||||
/// </summary>
|
||||
public IAuthenticationTokenProvider AccessTokenProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to know what the current clock time is when calculating or validating token expiration. When not assigned default is based on
|
||||
/// DateTimeOffset.UtcNow. This is typically needed only for unit testing.
|
||||
/// </summary>
|
||||
public ISystemClock SystemClock { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
"description": "ASP.NET 5 middleware that enables an application to support any standard OAuth 2.0 authentication workflow.",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Security": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security.DataProtection": "1.0.0-*"
|
||||
"Microsoft.AspNet.Security.DataProtection": "1.0.0-*",
|
||||
},
|
||||
"frameworks": {
|
||||
"aspnet50": {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>2755BFE5-7421-4A31-A644-F817DF5CAA98</ProjectGuid>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<UserProperties project_1json__JSONSchema="http://www.asp.net/media/4878834/project.json" />
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuthBearer
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
|
||||
internal sealed class NotNullAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Security.Notifications;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuthBearer
|
||||
{
|
||||
public class AuthenticationChallengeNotification<TOptions> : BaseNotification<TOptions>
|
||||
{
|
||||
public AuthenticationChallengeNotification(HttpContext context, TOptions options) : base(context, options)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Security.Notifications;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies events which the <see cref="OAuthBearerAuthenticationMiddleware"></see> invokes to enable developer control over the authentication process. />
|
||||
/// </summary>
|
||||
namespace Microsoft.AspNet.Security.OAuthBearer
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth bearer token middleware provider
|
||||
/// </summary>
|
||||
public class OAuthBearerAuthenticationNotifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OAuthBearerAuthenticationProvider"/> class
|
||||
/// </summary>
|
||||
public OAuthBearerAuthenticationNotifications()
|
||||
{
|
||||
ApplyChallenge = notification => { notification.HttpContext.Response.Headers.AppendValues("WWW-Authenticate", notification.Options.Challenge); return Task.FromResult(0); };
|
||||
AuthenticationFailed = notification => Task.FromResult(0);
|
||||
MessageReceived = notification => Task.FromResult(0);
|
||||
SecurityTokenReceived = notification => Task.FromResult(0);
|
||||
SecurityTokenValidated = notification => Task.FromResult(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed.
|
||||
/// </summary>
|
||||
public Func<AuthenticationFailedNotification<HttpContext, OAuthBearerAuthenticationOptions>, Task> AuthenticationFailed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a protocol message is first received.
|
||||
/// </summary>
|
||||
public Func<MessageReceivedNotification<HttpContext, OAuthBearerAuthenticationOptions>, Task> MessageReceived { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked with the security token that has been extracted from the protocol message.
|
||||
/// </summary>
|
||||
public Func<SecurityTokenReceivedNotification<HttpContext, OAuthBearerAuthenticationOptions>, Task> SecurityTokenReceived { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked after the security token has passed validation and a ClaimsIdentity has been generated.
|
||||
/// </summary>
|
||||
public Func<SecurityTokenValidatedNotification<HttpContext, OAuthBearerAuthenticationOptions>, Task> SecurityTokenValidated { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked to apply a challenge sent back to the caller.
|
||||
/// </summary>
|
||||
public Func<AuthenticationChallengeNotification<OAuthBearerAuthenticationOptions>, Task> ApplyChallenge { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuth
|
||||
namespace Microsoft.AspNet.Security.OAuthBearer
|
||||
{
|
||||
/// <summary>
|
||||
/// Default values used by authorization server and bearer authentication.
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Security.OAuth;
|
||||
using Microsoft.AspNet.Security.OAuthBearer;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IdentityModel.Tokens;
|
||||
using System.Linq;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Security;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
using Microsoft.AspNet.Security.Notifications;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.IdentityModel.Protocols;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuthBearer
|
||||
{
|
||||
public class OAuthBearerAuthenticationHandler : AuthenticationHandler<OAuthBearerAuthenticationOptions>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private OpenIdConnectConfiguration _configuration;
|
||||
|
||||
public OAuthBearerAuthenticationHandler(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override AuthenticationTicket AuthenticateCore()
|
||||
{
|
||||
return AuthenticateCoreAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches the 'Authorization' header for a 'Bearer' token. If the 'Bearer' token is found, it is validated using <see cref="TokenValidationParameters"/> set in the options.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
|
||||
{
|
||||
ExceptionDispatchInfo authFailedEx = null;
|
||||
string token = null;
|
||||
try
|
||||
{
|
||||
// Give application opportunity to find from a different location, adjust, or reject token
|
||||
var messageReceivedNotification =
|
||||
new MessageReceivedNotification<HttpContext, OAuthBearerAuthenticationOptions>(Context, Options)
|
||||
{
|
||||
ProtocolMessage = Context,
|
||||
};
|
||||
|
||||
// notification can set the token
|
||||
await Options.Notifications.MessageReceived(messageReceivedNotification);
|
||||
if (messageReceivedNotification.HandledResponse)
|
||||
{
|
||||
return messageReceivedNotification.AuthenticationTicket;
|
||||
}
|
||||
|
||||
if (messageReceivedNotification.Skipped)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string authorization = Request.Headers.Get("Authorization");
|
||||
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
token = authorization.Substring("Bearer ".Length).Trim();
|
||||
}
|
||||
|
||||
// If no token found, no further work possible
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// notify user token was received
|
||||
var securityTokenReceivedNotification =
|
||||
new SecurityTokenReceivedNotification<HttpContext, OAuthBearerAuthenticationOptions>(Context, Options)
|
||||
{
|
||||
ProtocolMessage = Context,
|
||||
SecurityToken = token,
|
||||
};
|
||||
|
||||
await Options.Notifications.SecurityTokenReceived(securityTokenReceivedNotification);
|
||||
if (securityTokenReceivedNotification.HandledResponse)
|
||||
{
|
||||
return securityTokenReceivedNotification.AuthenticationTicket;
|
||||
}
|
||||
|
||||
if (securityTokenReceivedNotification.Skipped)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_configuration == null && Options.ConfigurationManager != null)
|
||||
{
|
||||
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
|
||||
}
|
||||
|
||||
var validationParameters = Options.TokenValidationParameters.Clone();
|
||||
if (_configuration != null)
|
||||
{
|
||||
if (validationParameters.ValidIssuer == null && !string.IsNullOrWhiteSpace(_configuration.Issuer))
|
||||
{
|
||||
validationParameters.ValidIssuer = _configuration.Issuer;
|
||||
}
|
||||
else
|
||||
{
|
||||
IEnumerable<string> issuers = new[] { _configuration.Issuer };
|
||||
validationParameters.ValidIssuers = (validationParameters.ValidIssuers == null ? issuers : validationParameters.ValidIssuers.Concat(issuers));
|
||||
}
|
||||
|
||||
validationParameters.IssuerSigningKeys = (validationParameters.IssuerSigningKeys == null ? _configuration.SigningKeys : validationParameters.IssuerSigningKeys.Concat(_configuration.SigningKeys));
|
||||
}
|
||||
|
||||
SecurityToken validatedToken;
|
||||
foreach (var validator in Options.SecurityTokenValidators)
|
||||
{
|
||||
if (validator.CanReadToken(token))
|
||||
{
|
||||
ClaimsPrincipal principal = validator.ValidateToken(token, validationParameters, out validatedToken);
|
||||
AuthenticationTicket ticket = new AuthenticationTicket(principal, new AuthenticationProperties(), Options.AuthenticationType);
|
||||
var securityTokenValidatedNotification = new SecurityTokenValidatedNotification<HttpContext, OAuthBearerAuthenticationOptions>(Context, Options)
|
||||
{
|
||||
ProtocolMessage = Context,
|
||||
AuthenticationTicket = ticket
|
||||
};
|
||||
|
||||
if (securityTokenReceivedNotification.HandledResponse)
|
||||
{
|
||||
return securityTokenValidatedNotification.AuthenticationTicket;
|
||||
}
|
||||
|
||||
if (securityTokenReceivedNotification.Skipped)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ticket;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("No SecurityTokenValidator available for token: " + token ?? "null");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// We can't await inside a catch block, capture and handle outside.
|
||||
authFailedEx = ExceptionDispatchInfo.Capture(ex);
|
||||
}
|
||||
|
||||
if (authFailedEx != null)
|
||||
{
|
||||
_logger.WriteError("Exception occurred while processing message", authFailedEx.SourceException);
|
||||
|
||||
// Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the notification.
|
||||
if (Options.RefreshOnIssuerKeyNotFound && authFailedEx.SourceException.GetType().Equals(typeof(SecurityTokenSignatureKeyNotFoundException)))
|
||||
{
|
||||
Options.ConfigurationManager.RequestRefresh();
|
||||
}
|
||||
|
||||
var authenticationFailedNotification =
|
||||
new AuthenticationFailedNotification<HttpContext, OAuthBearerAuthenticationOptions>(Context, Options)
|
||||
{
|
||||
ProtocolMessage = Context,
|
||||
Exception = authFailedEx.SourceException
|
||||
};
|
||||
|
||||
await Options.Notifications.AuthenticationFailed(authenticationFailedNotification);
|
||||
if (authenticationFailedNotification.HandledResponse)
|
||||
{
|
||||
return authenticationFailedNotification.AuthenticationTicket;
|
||||
}
|
||||
|
||||
if (authenticationFailedNotification.Skipped)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
authFailedEx.Throw();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override void ApplyResponseChallenge()
|
||||
{
|
||||
ApplyResponseChallengeAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
protected override async Task ApplyResponseChallengeAsync()
|
||||
{
|
||||
await Options.Notifications.ApplyChallenge(new AuthenticationChallengeNotification<OAuthBearerAuthenticationOptions>(Context, Options));
|
||||
}
|
||||
|
||||
protected override void ApplyResponseGrant()
|
||||
{
|
||||
// N/A
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IdentityModel.Tokens;
|
||||
using System.Net.Http;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Security.DataProtection;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Microsoft.IdentityModel.Protocols;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuthBearer
|
||||
{
|
||||
/// <summary>
|
||||
/// Bearer authentication middleware component which is added to an HTTP pipeline. This class is not
|
||||
/// created by application code directly, instead it is added by calling the the IAppBuilder UseOAuthBearerAuthentication
|
||||
/// extension method.
|
||||
/// </summary>
|
||||
public class OAuthBearerAuthenticationMiddleware : AuthenticationMiddleware<OAuthBearerAuthenticationOptions>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Bearer authentication component which is added to an HTTP pipeline. This constructor is not
|
||||
/// called by application code directly, instead it is added by calling the the IAppBuilder UseOAuthBearerAuthentication
|
||||
/// extension method.
|
||||
/// </summary>
|
||||
public OAuthBearerAuthenticationMiddleware(
|
||||
RequestDelegate next,
|
||||
IServiceProvider services,
|
||||
ILoggerFactory loggerFactory,
|
||||
IOptions<OAuthBearerAuthenticationOptions> options,
|
||||
ConfigureOptions<OAuthBearerAuthenticationOptions> configureOptions)
|
||||
: base(next, services, options, configureOptions)
|
||||
{
|
||||
_logger = loggerFactory.Create<OAuthBearerAuthenticationMiddleware>();
|
||||
if (Options.Notifications == null)
|
||||
{
|
||||
Options.Notifications = new OAuthBearerAuthenticationNotifications();
|
||||
}
|
||||
|
||||
if (Options.SecurityTokenValidators == null)
|
||||
{
|
||||
Options.SecurityTokenValidators = new List<ISecurityTokenValidator> { new JwtSecurityTokenHandler() };
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Options.TokenValidationParameters.ValidAudience) && !string.IsNullOrWhiteSpace(Options.Audience))
|
||||
{
|
||||
Options.TokenValidationParameters.ValidAudience = Options.Audience;
|
||||
}
|
||||
|
||||
if (Options.ConfigurationManager == null)
|
||||
{
|
||||
if (Options.Configuration != null)
|
||||
{
|
||||
Options.ConfigurationManager = new StaticConfigurationManager<OpenIdConnectConfiguration>(Options.Configuration);
|
||||
}
|
||||
else if (!(string.IsNullOrWhiteSpace(Options.MetadataAddress) && string.IsNullOrWhiteSpace(Options.Authority)))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Options.MetadataAddress) && !string.IsNullOrWhiteSpace(Options.Authority))
|
||||
{
|
||||
Options.MetadataAddress = Options.Authority;
|
||||
if (!Options.MetadataAddress.EndsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
Options.MetadataAddress += "/";
|
||||
}
|
||||
|
||||
Options.MetadataAddress += ".well-known/openid-configuration";
|
||||
}
|
||||
|
||||
HttpClient httpClient = new HttpClient(ResolveHttpMessageHandler(Options));
|
||||
httpClient.Timeout = Options.BackchannelTimeout;
|
||||
httpClient.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB
|
||||
|
||||
Options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(Options.MetadataAddress, httpClient);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by the AuthenticationMiddleware base class to create a per-request handler.
|
||||
/// </summary>
|
||||
/// <returns>A new instance of the request handler</returns>
|
||||
protected override AuthenticationHandler<OAuthBearerAuthenticationOptions> CreateHandler()
|
||||
{
|
||||
return new OAuthBearerAuthenticationHandler(_logger);
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Managed by caller")]
|
||||
private static HttpMessageHandler ResolveHttpMessageHandler(OAuthBearerAuthenticationOptions options)
|
||||
{
|
||||
HttpMessageHandler handler = options.BackchannelHttpHandler ??
|
||||
#if ASPNET50
|
||||
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;
|
||||
}
|
||||
#else
|
||||
new WinHttpHandler();
|
||||
#endif
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IdentityModel.Tokens;
|
||||
using System.Net.Http;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
using Microsoft.IdentityModel.Protocols;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuthBearer
|
||||
{
|
||||
/// <summary>
|
||||
/// Options class provides information needed to control Bearer Authentication middleware behavior
|
||||
/// </summary>
|
||||
public class OAuthBearerAuthenticationOptions : AuthenticationOptions
|
||||
{
|
||||
private ICollection<ISecurityTokenValidator> _securityTokenValidators;
|
||||
private TokenValidationParameters _tokenValidationParameters;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of bearer authentication options with default values.
|
||||
/// </summary>
|
||||
public OAuthBearerAuthenticationOptions() : base()
|
||||
{
|
||||
AuthenticationType = OAuthBearerAuthenticationDefaults.AuthenticationType;
|
||||
BackchannelTimeout = TimeSpan.FromMinutes(1);
|
||||
Challenge = OAuthBearerAuthenticationDefaults.AuthenticationType;
|
||||
Notifications = new OAuthBearerAuthenticationNotifications();
|
||||
RefreshOnIssuerKeyNotFound = true;
|
||||
SystemClock = new SystemClock();
|
||||
TokenValidationParameters = new TokenValidationParameters();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the discovery endpoint for obtaining metadata
|
||||
/// </summary>
|
||||
public string MetadataAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Authority to use when making OpenIdConnect calls.
|
||||
/// </summary>
|
||||
public string Authority { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the audience for any received JWT token.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The expected audience for any received JWT token.
|
||||
/// </value>
|
||||
public string Audience { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the challenge to put in the "WWW-Authenticate" header.
|
||||
/// </summary>
|
||||
/// TODO - brentschmaltz, should not be null.
|
||||
public string Challenge { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The object provided by the application to process events raised by the bearer authentication middleware.
|
||||
/// The application may implement the interface fully, or it may create an instance of OAuthBearerAuthenticationProvider
|
||||
/// and assign delegates only to the events it wants to process.
|
||||
/// </summary>
|
||||
public OAuthBearerAuthenticationNotifications Notifications { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The HttpMessageHandler used to retrieve metadata.
|
||||
/// This cannot be set at the same time as BackchannelCertificateValidator unless the value
|
||||
/// is a WebRequestHandler.
|
||||
/// </summary>
|
||||
public HttpMessageHandler BackchannelHttpHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timeout when using the backchannel to make an http call.
|
||||
/// </summary>
|
||||
public TimeSpan BackchannelTimeout { get; set; }
|
||||
|
||||
#if ASPNET50
|
||||
/// <summary>
|
||||
/// Gets or sets the a pinned certificate validator to use to validate the endpoints used
|
||||
/// when retrieving metadata.
|
||||
/// </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; }
|
||||
#endif
|
||||
/// <summary>
|
||||
/// Configuration provided directly by the developer. If provided, then MetadataAddress and the Backchannel properties
|
||||
/// will not be used. This information should not be updated during request processing.
|
||||
/// </summary>
|
||||
public OpenIdConnectConfiguration Configuration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Responsible for retrieving, caching, and refreshing the configuration from metadata.
|
||||
/// If not provided, then one will be created using the MetadataAddress and Backchannel properties.
|
||||
/// </summary>
|
||||
public IConfigurationManager<OpenIdConnectConfiguration> ConfigurationManager { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if a metadata refresh should be attempted after a SecurityTokenSignatureKeyNotFoundException. This allows for automatic
|
||||
/// recovery in the event of a signature key rollover. This is enabled by default.
|
||||
/// </summary>
|
||||
public bool RefreshOnIssuerKeyNotFound { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to know what the current clock time is when calculating or validating token expiration. When not assigned default is based on
|
||||
/// DateTimeOffset.UtcNow. This is typically needed only for unit testing.
|
||||
/// </summary>
|
||||
public ISystemClock SystemClock { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="SecurityTokenValidators"/> for validating tokens.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentNullException">if 'value' is null.</exception>
|
||||
public ICollection<ISecurityTokenValidator> SecurityTokenValidators
|
||||
{
|
||||
get
|
||||
{
|
||||
return _securityTokenValidators;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException("SecurityTokenValidators");
|
||||
}
|
||||
|
||||
_securityTokenValidators = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the TokenValidationParameters
|
||||
/// </summary>
|
||||
/// <remarks>Contains the types and definitions required for validating a token.</remarks>
|
||||
/// <exception cref="ArgumentNullException">if 'value' is null.</exception>
|
||||
public TokenValidationParameters TokenValidationParameters
|
||||
{
|
||||
get
|
||||
{
|
||||
return _tokenValidationParameters;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException("TokenValidationParameters");
|
||||
}
|
||||
|
||||
_tokenValidationParameters = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.33440
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuthBearer {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.AspNet.Security.OAuth.Resources", System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof(Resources)).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The '{0}' option must be provided..
|
||||
/// </summary>
|
||||
internal static string Exception_OptionMustBeProvided
|
||||
{
|
||||
get
|
||||
{
|
||||
return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler..
|
||||
/// </summary>
|
||||
internal static string Exception_ValidatorHandlerMismatch {
|
||||
get {
|
||||
return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Exception_OptionMustBeProvided" xml:space="preserve">
|
||||
<value>The '{0}' option must be provided.</value>
|
||||
</data>
|
||||
<data name="Exception_ValidatorHandlerMismatch" xml:space="preserve">
|
||||
<value>An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"version": "1.0.0-*",
|
||||
"description": "ASP.NET 5 middleware that enables an application to receive a OAuth bearer token.",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Security": "1.0.0-*",
|
||||
"Microsoft.IdentityModel.Protocol.Extensions": "2.0.0-beta1-*",
|
||||
"System.IdentityModel.Tokens": "5.0.0-beta1-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"aspnet50": {
|
||||
"frameworkAssemblies": {
|
||||
"System.Net.Http.WebRequest": "",
|
||||
"System.Net.Http": ""
|
||||
}
|
||||
},
|
||||
"aspnetcore50": {
|
||||
"dependencies": {
|
||||
"System.Net.Http.WinHttpHandler": "4.0.0-beta-*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>674d128e-83bb-481a-a9d9-6d47872e1fc8</ProjectGuid>
|
||||
<RootNamespace>Microsoft.AspNet.Security.OpenIdConnect</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<ProduceOutputsOnBuild>True</ProduceOutputsOnBuild>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<ProduceOutputsOnBuild>True</ProduceOutputsOnBuild>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<UserProperties Project_1json__JSONSchema="http://www.asp.net/media/4878834/project.json" />
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Security.OpenIdConnect
|
||||
{
|
||||
public interface INonceCache
|
||||
{
|
||||
string AddNonce(string nonce);
|
||||
bool TryRemoveNonce(string nonce);
|
||||
bool HasNonce(string nonce);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Security.OpenIdConnect;
|
||||
using Microsoft.IdentityModel.Protocols;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IdentityModel.Tokens;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// This Notification can be used to be informed when an 'AuthorizationCode' is received over the OpenIdConnect protocol.
|
||||
/// </summary>
|
||||
public class AuthorizationCodeReceivedNotification : BaseNotification<OpenIdConnectAuthenticationOptions>
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a <see cref="AuthorizationCodeReceivedNotification"/>
|
||||
/// </summary>
|
||||
public AuthorizationCodeReceivedNotification(HttpContext context, OpenIdConnectAuthenticationOptions options) : base(context, options)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the 'code'.
|
||||
/// </summary>
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="JwtSecurityToken"/> that was received in the id_token + code OpenIdConnectRequest.
|
||||
/// </summary>
|
||||
public JwtSecurityToken JwtSecurityToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="OpenIdConnectMessage"/>.
|
||||
/// </summary>
|
||||
public OpenIdConnectMessage ProtocolMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the 'redirect_uri'.
|
||||
/// </summary>
|
||||
/// <remarks>This is the redirect_uri that was sent in the id_token + code OpenIdConnectRequest.</remarks>
|
||||
[SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "user controlled, not necessarily a URI")]
|
||||
public string RedirectUri { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
|
||||
namespace Microsoft.AspNet.Security.OpenIdConnect
|
||||
{
|
||||
/// <summary>
|
||||
/// Default values related to OpenIdConnect authentication middleware
|
||||
/// </summary>
|
||||
public static class OpenIdConnectAuthenticationDefaults
|
||||
{
|
||||
/// <summary>
|
||||
/// The default value used for OpenIdConnectAuthenticationOptions.AuthenticationType
|
||||
/// </summary>
|
||||
public const string AuthenticationType = "OpenIdConnect";
|
||||
|
||||
/// <summary>
|
||||
/// The prefix used to provide a default OpenIdConnectAuthenticationOptions.CookieName
|
||||
/// </summary>
|
||||
public const string CookiePrefix = ".AspNet.OpenIdConnect.";
|
||||
|
||||
/// <summary>
|
||||
/// The default value for OpenIdConnectAuthenticationOptions.Caption.
|
||||
/// </summary>
|
||||
public const string Caption = "OpenIdConnect";
|
||||
|
||||
/// <summary>
|
||||
/// The prefix used to for the a nonce in the cookie
|
||||
/// </summary>
|
||||
internal const string CookieNoncePrefix = ".AspNet.OpenIdConnect.Nonce.";
|
||||
|
||||
/// <summary>
|
||||
/// The property for the RedirectUri that was used when asking for a 'authorizationCode'
|
||||
/// </summary>
|
||||
public const string RedirectUriUsedForCodeKey = "OpenIdConnect.Code.RedirectUri";
|
||||
|
||||
/// <summary>
|
||||
/// Constant used to identify state in openIdConnect protocal message
|
||||
/// </summary>
|
||||
internal const string AuthenticationPropertiesKey = "OpenIdConnect.AuthenticationProperties";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Security.OpenIdConnect;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
|
||||
namespace Microsoft.AspNet.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for using <see cref="OpenIdConnectAuthenticationMiddleware"/>
|
||||
/// </summary>
|
||||
public static class OpenIdConnectAuthenticationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the <see cref="OpenIdConnectAuthenticationMiddleware"/> into the ASP.NET runtime.
|
||||
/// </summary>
|
||||
/// <param name="app">The application builder</param>
|
||||
/// <param name="options">Options which control the processing of the OpenIdConnect protocol and token validation.</param>
|
||||
/// <returns>The application builder</returns>
|
||||
public static IApplicationBuilder UseOpenIdConnectAuthentication(this IApplicationBuilder app, Action<OpenIdConnectAuthenticationOptions> configureOptions = null, string optionsName = "")
|
||||
{
|
||||
return app.UseMiddleware<OpenIdConnectAuthenticationMiddleware>(
|
||||
new ConfigureOptions<OpenIdConnectAuthenticationOptions>(configureOptions ?? (o => { }))
|
||||
{
|
||||
Name = optionsName
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IdentityModel.Tokens;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Security.DataHandler;
|
||||
using Microsoft.AspNet.Security.DataHandler.Encoder;
|
||||
using Microsoft.AspNet.Security.DataHandler.Serializer;
|
||||
using Microsoft.AspNet.Security.DataProtection;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Microsoft.IdentityModel.Protocols;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OpenIdConnect
|
||||
{
|
||||
/// <summary>
|
||||
/// ASP.NET middleware for obtaining identities using OpenIdConnect protocol.
|
||||
/// </summary>
|
||||
public class OpenIdConnectAuthenticationMiddleware : AuthenticationMiddleware<OpenIdConnectAuthenticationOptions>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a <see cref="OpenIdConnectAuthenticationMiddleware"/>
|
||||
/// </summary>
|
||||
/// <param name="next">The next middleware in the ASP.NET pipeline to invoke</param>
|
||||
/// <param name="app">The ASP.NET application</param>
|
||||
/// <param name="options">Configuration options for the middleware</param>
|
||||
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Managed by caller")]
|
||||
public OpenIdConnectAuthenticationMiddleware(
|
||||
RequestDelegate next,
|
||||
IServiceProvider services,
|
||||
IDataProtectionProvider dataProtectionProvider,
|
||||
ILoggerFactory loggerFactory,
|
||||
IOptions<ExternalAuthenticationOptions> externalOptions,
|
||||
IOptions<OpenIdConnectAuthenticationOptions> options,
|
||||
ConfigureOptions<OpenIdConnectAuthenticationOptions> configureOptions)
|
||||
: base(next, services, options, configureOptions)
|
||||
{
|
||||
_logger = loggerFactory.Create<OpenIdConnectAuthenticationMiddleware>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Options.TokenValidationParameters.AuthenticationType))
|
||||
{
|
||||
Options.TokenValidationParameters.AuthenticationType = externalOptions.Options.SignInAsAuthenticationType;
|
||||
}
|
||||
|
||||
if (Options.StateDataFormat == null)
|
||||
{
|
||||
var dataProtector = dataProtectionProvider.CreateDataProtector(
|
||||
typeof(OpenIdConnectAuthenticationMiddleware).FullName,
|
||||
typeof(string).FullName,
|
||||
Options.AuthenticationType,
|
||||
"v1");
|
||||
|
||||
Options.StateDataFormat = new PropertiesDataFormat(dataProtector);
|
||||
}
|
||||
|
||||
if (Options.StringDataFormat == null)
|
||||
{
|
||||
var dataProtector = dataProtectionProvider.CreateDataProtector(
|
||||
typeof(OpenIdConnectAuthenticationMiddleware).FullName,
|
||||
typeof(string).FullName,
|
||||
Options.AuthenticationType,
|
||||
"v1");
|
||||
|
||||
Options.StringDataFormat = new SecureDataFormat<string>(new StringSerializer(), dataProtector, TextEncodings.Base64Url);
|
||||
}
|
||||
|
||||
if (Options.SecurityTokenValidators == null)
|
||||
{
|
||||
Options.SecurityTokenValidators = new Collection<ISecurityTokenValidator> { new JwtSecurityTokenHandler() };
|
||||
}
|
||||
|
||||
// if the user has not set the AuthorizeCallback, set it from the redirect_uri
|
||||
if (!Options.CallbackPath.HasValue)
|
||||
{
|
||||
Uri redirectUri;
|
||||
if (!string.IsNullOrEmpty(Options.RedirectUri) && Uri.TryCreate(Options.RedirectUri, UriKind.Absolute, out redirectUri))
|
||||
{
|
||||
// Redirect_Uri must be a very specific, case sensitive value, so we can't generate it. Instead we generate AuthorizeCallback from it.
|
||||
Options.CallbackPath = PathString.FromUriComponent(redirectUri);
|
||||
}
|
||||
}
|
||||
|
||||
if (Options.Notifications == null)
|
||||
{
|
||||
Options.Notifications = new OpenIdConnectAuthenticationNotifications();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Options.TokenValidationParameters.ValidAudience) && !string.IsNullOrWhiteSpace(Options.ClientId))
|
||||
{
|
||||
Options.TokenValidationParameters.ValidAudience = Options.ClientId;
|
||||
}
|
||||
|
||||
if (Options.ConfigurationManager == null)
|
||||
{
|
||||
if (Options.Configuration != null)
|
||||
{
|
||||
Options.ConfigurationManager = new StaticConfigurationManager<OpenIdConnectConfiguration>(Options.Configuration);
|
||||
}
|
||||
else if (!(string.IsNullOrWhiteSpace(Options.MetadataAddress) && string.IsNullOrWhiteSpace(Options.Authority)))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Options.MetadataAddress) && !string.IsNullOrWhiteSpace(Options.Authority))
|
||||
{
|
||||
Options.MetadataAddress = Options.Authority;
|
||||
if (!Options.MetadataAddress.EndsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
Options.MetadataAddress += "/";
|
||||
}
|
||||
|
||||
Options.MetadataAddress += ".well-known/openid-configuration";
|
||||
}
|
||||
|
||||
HttpClient httpClient = new HttpClient(ResolveHttpMessageHandler(Options));
|
||||
httpClient.Timeout = Options.BackchannelTimeout;
|
||||
httpClient.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB
|
||||
Options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(Options.MetadataAddress, httpClient);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the <see cref="AuthenticationHandler"/> object for processing authentication-related requests.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="AuthenticationHandler"/> configured with the <see cref="OpenIdConnectAuthenticationOptions"/> supplied to the constructor.</returns>
|
||||
protected override AuthenticationHandler<OpenIdConnectAuthenticationOptions> CreateHandler()
|
||||
{
|
||||
return new OpenIdConnectAuthenticationHandler(_logger);
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Managed by caller")]
|
||||
private static HttpMessageHandler ResolveHttpMessageHandler(OpenIdConnectAuthenticationOptions options)
|
||||
{
|
||||
HttpMessageHandler handler = options.BackchannelHttpHandler ??
|
||||
#if ASPNET50
|
||||
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;
|
||||
}
|
||||
#else
|
||||
new WinHttpHandler();
|
||||
#endif
|
||||
return handler;
|
||||
}
|
||||
|
||||
private class StringSerializer : IDataSerializer<string>
|
||||
{
|
||||
public string Deserialize(byte[] data)
|
||||
{
|
||||
return Encoding.UTF8.GetString(data);
|
||||
}
|
||||
|
||||
public byte[] Serialize(string model)
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Security.Notifications;
|
||||
using Microsoft.IdentityModel.Protocols;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OpenIdConnect
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies events which the <see cref="OpenIdConnectAuthenticationMiddleware" />invokes to enable developer control over the authentication process.
|
||||
/// </summary>
|
||||
public class OpenIdConnectAuthenticationNotifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new set of notifications. Each notification has a default no-op behavior unless otherwise documented.
|
||||
/// </summary>
|
||||
public OpenIdConnectAuthenticationNotifications()
|
||||
{
|
||||
AuthenticationFailed = notification => Task.FromResult(0);
|
||||
AuthorizationCodeReceived = notification => Task.FromResult(0);
|
||||
MessageReceived = notification => Task.FromResult(0);
|
||||
SecurityTokenReceived = notification => Task.FromResult(0);
|
||||
SecurityTokenValidated = notification => Task.FromResult(0);
|
||||
RedirectToIdentityProvider = notification => Task.FromResult(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed.
|
||||
/// </summary>
|
||||
public Func<AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>, Task> AuthenticationFailed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked after security token validation if an authorization code is present in the protocol message.
|
||||
/// </summary>
|
||||
public Func<AuthorizationCodeReceivedNotification, Task> AuthorizationCodeReceived { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a protocol message is first received.
|
||||
/// </summary>
|
||||
public Func<MessageReceivedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>, Task> MessageReceived { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked to manipulate redirects to the identity provider for SignIn, SignOut, or Challenge.
|
||||
/// </summary>
|
||||
public Func<RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>, Task> RedirectToIdentityProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked with the security token that has been extracted from the protocol message.
|
||||
/// </summary>
|
||||
public Func<SecurityTokenReceivedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>, Task> SecurityTokenReceived { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked after the security token has passed validation and a ClaimsIdentity has been generated.
|
||||
/// </summary>
|
||||
public Func<SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>, Task> SecurityTokenValidated { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,337 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IdentityModel.Tokens;
|
||||
using System.Net.Http;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Security;
|
||||
using Microsoft.IdentityModel.Protocols;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OpenIdConnect
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration options for <see cref="OpenIdConnectAuthenticationOptions"/>
|
||||
/// </summary>
|
||||
public class OpenIdConnectAuthenticationOptions : AuthenticationOptions
|
||||
{
|
||||
private TimeSpan _backchannelTimeout;
|
||||
private OpenIdConnectProtocolValidator _protocolValidator;
|
||||
private ICollection<ISecurityTokenValidator> _securityTokenValidators;
|
||||
private ISecureDataFormat<AuthenticationProperties> _stateDataFormat;
|
||||
private ISecureDataFormat<string> _stringDataFormat;
|
||||
private TokenValidationParameters _tokenValidationParameters;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="OpenIdConnectAuthenticationOptions"/>
|
||||
/// </summary>
|
||||
public OpenIdConnectAuthenticationOptions()
|
||||
: this(OpenIdConnectAuthenticationDefaults.AuthenticationType)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="OpenIdConnectAuthenticationOptions"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Defaults:
|
||||
/// <para>AddNonceToRequest: true.</para>
|
||||
/// <para>AuthenticationMode: <see cref="AuthenticationMode.Active"/>.</para>
|
||||
/// <para>BackchannelTimeout: 1 minute.</para>
|
||||
/// <para>Caption: <see cref="OpenIdConnectAuthenticationDefaults.Caption"/>.</para>
|
||||
/// <para>ProtocolValidator: new <see cref="OpenIdConnectProtocolValidator"/>.</para>
|
||||
/// <para>RefreshOnIssuerKeyNotFound: true</para>
|
||||
/// <para>ResponseType: <see cref="OpenIdConnectResponseTypes.CodeIdToken"/></para>
|
||||
/// <para>Scope: <see cref="OpenIdConnectScopes.OpenIdProfile"/>.</para>
|
||||
/// <para>TokenValidationParameters: new <see cref="TokenValidationParameters"/> with AuthenticationType = authenticationType.</para>
|
||||
/// <para>UseTokenLifetime: true.</para>
|
||||
/// </remarks>
|
||||
/// <param name="authenticationType"> will be used to when creating the <see cref="System.Security.Claims.ClaimsIdentity"/> for the AuthenticationType property.</param>
|
||||
[SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationOptions.set_Caption(System.String)", Justification = "Not a LOC field")]
|
||||
public OpenIdConnectAuthenticationOptions(string authenticationType)
|
||||
{
|
||||
AuthenticationMode = AuthenticationMode.Active;
|
||||
AuthenticationType = authenticationType;
|
||||
BackchannelTimeout = TimeSpan.FromMinutes(1);
|
||||
Caption = OpenIdConnectAuthenticationDefaults.Caption;
|
||||
ProtocolValidator = new OpenIdConnectProtocolValidator();
|
||||
RefreshOnIssuerKeyNotFound = true;
|
||||
ResponseType = OpenIdConnectResponseTypes.CodeIdToken;
|
||||
Scope = OpenIdConnectScopes.OpenIdProfile;
|
||||
TokenValidationParameters = new TokenValidationParameters();
|
||||
UseTokenLifetime = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Authority to use when making OpenIdConnect calls.
|
||||
/// </summary>
|
||||
public string Authority { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An optional constrained path on which to process the authentication callback.
|
||||
/// If not provided and RedirectUri is available, this value will be generated from RedirectUri.
|
||||
/// </summary>
|
||||
/// <remarks>If you set this value, then the <see cref="OpenIdConnectAuthenticationHandler"/> will only listen for posts at this address.
|
||||
/// If the IdentityProvider does not post to this address, you may end up in a 401 -> IdentityProvider -> Client -> 401 -> ...</remarks>
|
||||
public PathString CallbackPath { get; set; }
|
||||
|
||||
#if ASPNET50
|
||||
/// <summary>
|
||||
/// Gets or sets the a pinned certificate validator to use to validate the endpoints used
|
||||
/// when retrieving metadata.
|
||||
/// </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; }
|
||||
#endif
|
||||
/// <summary>
|
||||
/// The HttpMessageHandler used to retrieve metadata.
|
||||
/// This cannot be set at the same time as BackchannelCertificateValidator unless the value
|
||||
/// is a WebRequestHandler.
|
||||
/// </summary>
|
||||
public HttpMessageHandler BackchannelHttpHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timeout when using the backchannel to make an http call.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "By design we use the property name in the exception")]
|
||||
public TimeSpan BackchannelTimeout
|
||||
{
|
||||
get
|
||||
{
|
||||
return _backchannelTimeout;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value <= TimeSpan.Zero)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("BackchannelTimeout", value, Resources.ArgsException_BackchallelLessThanZero);
|
||||
}
|
||||
|
||||
_backchannelTimeout = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Gets or sets the 'client_id'.
|
||||
/// </summary>
|
||||
public string ClientId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the 'client_secret'.
|
||||
/// </summary>
|
||||
public string ClientSecret { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Configuration provided directly by the developer. If provided, then MetadataAddress and the Backchannel properties
|
||||
/// will not be used. This information should not be updated during request processing.
|
||||
/// </summary>
|
||||
public OpenIdConnectConfiguration Configuration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The OpenIdConnect protocol http://openid.net/specs/openid-connect-core-1_0.html
|
||||
/// recommends adding a nonce to a request as a mitigation against replay attacks when requesting id_tokens.
|
||||
/// By default the runtime uses cookies with unique names generated from a hash of the nonce.
|
||||
/// </summary>
|
||||
public INonceCache NoneCache { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the discovery endpoint for obtaining metadata
|
||||
/// </summary>
|
||||
public string MetadataAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the expected audience for any received JWT token.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The expected audience for any received JWT token.
|
||||
/// </value>
|
||||
public string Audience { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Responsible for retrieving, caching, and refreshing the configuration from metadata.
|
||||
/// If not provided, then one will be created using the MetadataAddress and Backchannel properties.
|
||||
/// </summary>
|
||||
public IConfigurationManager<OpenIdConnectConfiguration> ConfigurationManager { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if a metadata refresh should be attempted after a SecurityTokenSignatureKeyNotFoundException. This allows for automatic
|
||||
/// recovery in the event of a signature key rollover. This is enabled by default.
|
||||
/// </summary>
|
||||
public bool RefreshOnIssuerKeyNotFound { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="OpenIdConnectAuthenticationNotifications"/> to notify when processing OpenIdConnect messages.
|
||||
/// </summary>
|
||||
public OpenIdConnectAuthenticationNotifications Notifications { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="OpenIdConnectProtocolValidator"/> that is used ensure the 'id_token' received
|
||||
/// is valid per: http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentNullException">if 'value' is null.</exception>
|
||||
public OpenIdConnectProtocolValidator ProtocolValidator
|
||||
{
|
||||
get
|
||||
{
|
||||
return _protocolValidator;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException("value");
|
||||
}
|
||||
|
||||
_protocolValidator = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the 'post_logout_redirect_uri'
|
||||
/// </summary>
|
||||
/// <remarks>This is sent to the OP as the redirect for the user-agent.</remarks>
|
||||
[SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "By design")]
|
||||
[SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Logout", Justification = "This is the term used in the spec.")]
|
||||
public string PostLogoutRedirectUri { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the 'redirect_uri'.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "By Design")]
|
||||
public string RedirectUri { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the 'resource'.
|
||||
/// </summary>
|
||||
public string Resource { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the 'response_type'.
|
||||
/// </summary>
|
||||
public string ResponseType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the 'scope'.
|
||||
/// </summary>
|
||||
public string Scope { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the AuthenticationType used when creating the <see cref="System.Security.Claims.ClaimsIdentity"/>.
|
||||
/// </summary>
|
||||
public string SignInAsAuthenticationType
|
||||
{
|
||||
get { return TokenValidationParameters.AuthenticationType; }
|
||||
set { TokenValidationParameters.AuthenticationType = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type used to secure data handled by the middleware.
|
||||
/// </summary>
|
||||
public ISecureDataFormat<AuthenticationProperties> StateDataFormat
|
||||
{
|
||||
get
|
||||
{
|
||||
return _stateDataFormat;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException("value");
|
||||
}
|
||||
|
||||
_stateDataFormat = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type used to secure strings used by the middleware.
|
||||
// </summary>
|
||||
public ISecureDataFormat<string> StringDataFormat
|
||||
{
|
||||
get
|
||||
{
|
||||
return _stringDataFormat;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException("value");
|
||||
}
|
||||
|
||||
_stringDataFormat = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="SecurityTokenValidators"/> for validating tokens.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentNullException">if 'value' is null.</exception>
|
||||
public ICollection<ISecurityTokenValidator> SecurityTokenValidators
|
||||
{
|
||||
get
|
||||
{
|
||||
return _securityTokenValidators;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException("SecurityTokenValidators");
|
||||
}
|
||||
|
||||
_securityTokenValidators = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the TokenValidationParameters
|
||||
/// </summary>
|
||||
/// <remarks>Contains the types and definitions required for validating a token.</remarks>
|
||||
public TokenValidationParameters TokenValidationParameters
|
||||
{
|
||||
get
|
||||
{
|
||||
return _tokenValidationParameters;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException("value");
|
||||
}
|
||||
|
||||
_tokenValidationParameters = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the authentication session lifetime (e.g. cookies) should match that of the authentication token.
|
||||
/// If the token does not provide lifetime information then normal session lifetimes will be used.
|
||||
/// This is enabled by default.
|
||||
/// </summary>
|
||||
public bool UseTokenLifetime
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,577 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IdentityModel.Tokens;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Security;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
using Microsoft.AspNet.Security.Notifications;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.IdentityModel.Protocols;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OpenIdConnect
|
||||
{
|
||||
/// <summary>
|
||||
/// A per-request authentication handler for the OpenIdConnectAuthenticationMiddleware.
|
||||
/// </summary>
|
||||
public class OpenIdConnectAuthenticationHandler : AuthenticationHandler<OpenIdConnectAuthenticationOptions>
|
||||
{
|
||||
private const string NonceProperty = "N";
|
||||
private const string UriSchemeDelimiter = "://";
|
||||
private readonly ILogger _logger;
|
||||
private OpenIdConnectConfiguration _configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new OpenIdConnectAuthenticationHandler
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
public OpenIdConnectAuthenticationHandler(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private string CurrentUri
|
||||
{
|
||||
get
|
||||
{
|
||||
return Request.Scheme +
|
||||
UriSchemeDelimiter +
|
||||
Request.Host +
|
||||
Request.PathBase +
|
||||
Request.Path +
|
||||
Request.QueryString;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyResponseGrant()
|
||||
{
|
||||
ApplyResponseGrantAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles Signout
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override async Task ApplyResponseGrantAsync()
|
||||
{
|
||||
var signout = SignOutContext;
|
||||
if (signout != null)
|
||||
{
|
||||
if (_configuration == null && Options.ConfigurationManager != null)
|
||||
{
|
||||
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
|
||||
}
|
||||
|
||||
OpenIdConnectMessage openIdConnectMessage = new OpenIdConnectMessage()
|
||||
{
|
||||
IssuerAddress = _configuration == null ? string.Empty : (_configuration.EndSessionEndpoint ?? string.Empty),
|
||||
RequestType = OpenIdConnectRequestType.LogoutRequest,
|
||||
};
|
||||
|
||||
// Set End_Session_Endpoint in order:
|
||||
// 1. properties.Redirect
|
||||
// 2. Options.Wreply
|
||||
AuthenticationProperties properties = new AuthenticationProperties(); // TODO signout.Properties;
|
||||
if (properties != null && !string.IsNullOrEmpty(properties.RedirectUri))
|
||||
{
|
||||
openIdConnectMessage.PostLogoutRedirectUri = properties.RedirectUri;
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(Options.PostLogoutRedirectUri))
|
||||
{
|
||||
openIdConnectMessage.PostLogoutRedirectUri = Options.PostLogoutRedirectUri;
|
||||
}
|
||||
|
||||
var notification = new RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
|
||||
{
|
||||
ProtocolMessage = openIdConnectMessage
|
||||
};
|
||||
await Options.Notifications.RedirectToIdentityProvider(notification);
|
||||
|
||||
if (!notification.HandledResponse)
|
||||
{
|
||||
string redirectUri = notification.ProtocolMessage.CreateLogoutRequestUrl();
|
||||
if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
|
||||
{
|
||||
_logger.WriteWarning("The logout redirect URI is malformed: " + redirectUri);
|
||||
}
|
||||
Response.Redirect(redirectUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyResponseChallenge()
|
||||
{
|
||||
ApplyResponseChallengeAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Responds to a 401 Challenge sends an OpenIdConnect message to the 'identity authority' to obtain an identity.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override async Task ApplyResponseChallengeAsync()
|
||||
{
|
||||
if ((Response.StatusCode != 401) || (ChallengeContext == null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// order for redirect_uri
|
||||
// 1. challenge.Properties.RedirectUri
|
||||
// 2. CurrentUri
|
||||
AuthenticationProperties properties = new AuthenticationProperties(ChallengeContext.Properties);
|
||||
if (string.IsNullOrEmpty(properties.RedirectUri))
|
||||
{
|
||||
properties.RedirectUri = CurrentUri;
|
||||
}
|
||||
|
||||
// this value will be passed to the AuthorizationCodeReceivedNotification
|
||||
if (!string.IsNullOrWhiteSpace(Options.RedirectUri))
|
||||
{
|
||||
properties.Dictionary.Add(OpenIdConnectAuthenticationDefaults.RedirectUriUsedForCodeKey, Options.RedirectUri);
|
||||
}
|
||||
|
||||
if (_configuration == null && Options.ConfigurationManager != null)
|
||||
{
|
||||
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
|
||||
}
|
||||
|
||||
OpenIdConnectMessage openIdConnectMessage = new OpenIdConnectMessage
|
||||
{
|
||||
ClientId = Options.ClientId,
|
||||
IssuerAddress = _configuration == null ? string.Empty : (_configuration.AuthorizationEndpoint ?? string.Empty),
|
||||
RedirectUri = Options.RedirectUri,
|
||||
RequestType = OpenIdConnectRequestType.AuthenticationRequest,
|
||||
Resource = Options.Resource,
|
||||
ResponseMode = OpenIdConnectResponseModes.FormPost,
|
||||
ResponseType = Options.ResponseType,
|
||||
Scope = Options.Scope,
|
||||
State = OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey + "=" + Uri.EscapeDataString(Options.StateDataFormat.Protect(properties))
|
||||
};
|
||||
|
||||
// TODO - brentschmaltz, if INonceCache is set should we even consider if ProtocolValidator is set?
|
||||
if (Options.ProtocolValidator.RequireNonce)
|
||||
{
|
||||
openIdConnectMessage.Nonce = Options.ProtocolValidator.GenerateNonce();
|
||||
if (Options.NoneCache != null)
|
||||
{
|
||||
Options.NoneCache.AddNonce(openIdConnectMessage.Nonce);
|
||||
}
|
||||
else
|
||||
{
|
||||
RememberNonce(openIdConnectMessage.Nonce);
|
||||
}
|
||||
}
|
||||
|
||||
var notification = new RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
|
||||
{
|
||||
ProtocolMessage = openIdConnectMessage
|
||||
};
|
||||
|
||||
await Options.Notifications.RedirectToIdentityProvider(notification);
|
||||
if (!notification.HandledResponse)
|
||||
{
|
||||
string redirectUri = notification.ProtocolMessage.CreateAuthenticationRequestUrl();
|
||||
if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
|
||||
{
|
||||
_logger.WriteWarning("The authenticate redirect URI is malformed: " + redirectUri);
|
||||
}
|
||||
|
||||
Response.Redirect(redirectUri);
|
||||
}
|
||||
}
|
||||
|
||||
protected override AuthenticationTicket AuthenticateCore()
|
||||
{
|
||||
return AuthenticateCoreAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked to process incoming OpenIdConnect messages.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="AuthenticationTicket"/> if successful.</returns>
|
||||
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
|
||||
{
|
||||
// Allow login to be constrained to a specific path. Need to make this runtime configurable.
|
||||
if (Options.CallbackPath.HasValue && Options.CallbackPath != (Request.PathBase + Request.Path))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
OpenIdConnectMessage openIdConnectMessage = null;
|
||||
|
||||
// assumption: if the ContentType is "application/x-www-form-urlencoded" it should be safe to read as it is small.
|
||||
if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.IsNullOrWhiteSpace(Request.ContentType)
|
||||
// May have media/type; charset=utf-8, allow partial match.
|
||||
&& Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)
|
||||
&& Request.Body.CanRead)
|
||||
{
|
||||
IFormCollection form = await Request.ReadFormAsync();
|
||||
Request.Body.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// TODO: a delegate on OpenIdConnectAuthenticationOptions would allow for users to hook their own custom message.
|
||||
openIdConnectMessage = new OpenIdConnectMessage(form);
|
||||
}
|
||||
|
||||
if (openIdConnectMessage == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
ExceptionDispatchInfo authFailedEx = null;
|
||||
try
|
||||
{
|
||||
var messageReceivedNotification = new MessageReceivedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
|
||||
{
|
||||
ProtocolMessage = openIdConnectMessage
|
||||
};
|
||||
|
||||
await Options.Notifications.MessageReceived(messageReceivedNotification);
|
||||
if (messageReceivedNotification.HandledResponse)
|
||||
{
|
||||
return messageReceivedNotification.AuthenticationTicket;
|
||||
}
|
||||
|
||||
if (messageReceivedNotification.Skipped)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// runtime always adds state, if we don't find it OR we failed to 'unprotect' it this is not a message we
|
||||
// should process.
|
||||
AuthenticationProperties properties = GetPropertiesFromState(openIdConnectMessage.State);
|
||||
if (properties == null)
|
||||
{
|
||||
_logger.WriteWarning("The state field is missing or invalid.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// devs will need to hook AuthenticationFailedNotification to avoid having 'raw' runtime errors displayed to users.
|
||||
if (!string.IsNullOrWhiteSpace(openIdConnectMessage.Error))
|
||||
{
|
||||
throw new OpenIdConnectProtocolException(
|
||||
string.Format(CultureInfo.InvariantCulture,
|
||||
openIdConnectMessage.Error,
|
||||
Resources.Exception_OpenIdConnectMessageError, openIdConnectMessage.ErrorDescription ?? string.Empty, openIdConnectMessage.ErrorUri ?? string.Empty));
|
||||
}
|
||||
|
||||
// code is only accepted with id_token, in this version, hence check for code is inside this if
|
||||
// OpenIdConnect protocol allows a Code to be received without the id_token
|
||||
if (string.IsNullOrWhiteSpace(openIdConnectMessage.IdToken))
|
||||
{
|
||||
_logger.WriteWarning("The id_token is missing.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var securityTokenReceivedNotification = new SecurityTokenReceivedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
|
||||
{
|
||||
ProtocolMessage = openIdConnectMessage
|
||||
};
|
||||
|
||||
await Options.Notifications.SecurityTokenReceived(securityTokenReceivedNotification);
|
||||
if (securityTokenReceivedNotification.HandledResponse)
|
||||
{
|
||||
return securityTokenReceivedNotification.AuthenticationTicket;
|
||||
}
|
||||
|
||||
if (securityTokenReceivedNotification.Skipped)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_configuration == null && Options.ConfigurationManager != null)
|
||||
{
|
||||
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
|
||||
}
|
||||
|
||||
// Copy and augment to avoid cross request race conditions for updated configurations.
|
||||
TokenValidationParameters validationParameters = Options.TokenValidationParameters.Clone();
|
||||
if (_configuration != null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(validationParameters.ValidIssuer))
|
||||
{
|
||||
validationParameters.ValidIssuer = _configuration.Issuer;
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(_configuration.Issuer))
|
||||
{
|
||||
validationParameters.ValidIssuers = (validationParameters.ValidIssuers == null ? new[] { _configuration.Issuer } : validationParameters.ValidIssuers.Concat(new[] { _configuration.Issuer }));
|
||||
}
|
||||
|
||||
validationParameters.IssuerSigningKeys = (validationParameters.IssuerSigningKeys == null ? _configuration.SigningKeys : validationParameters.IssuerSigningKeys.Concat(_configuration.SigningKeys));
|
||||
}
|
||||
|
||||
AuthenticationTicket ticket;
|
||||
SecurityToken validatedToken = null;
|
||||
ClaimsPrincipal principal = null;
|
||||
JwtSecurityToken jwt = null;
|
||||
|
||||
foreach (var validator in Options.SecurityTokenValidators)
|
||||
{
|
||||
if (validator.CanReadToken(openIdConnectMessage.IdToken))
|
||||
{
|
||||
principal = validator.ValidateToken(openIdConnectMessage.IdToken, validationParameters, out validatedToken);
|
||||
jwt = validatedToken as JwtSecurityToken;
|
||||
if (jwt == null)
|
||||
{
|
||||
throw new InvalidOperationException("Validated Security Token must be a JwtSecurityToken was: " + (validatedToken == null ? "null" : validatedToken.GetType().ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (validatedToken == null)
|
||||
{
|
||||
throw new InvalidOperationException("No SecurityTokenValidator found for token: " + openIdConnectMessage.IdToken);
|
||||
}
|
||||
|
||||
ticket = new AuthenticationTicket(principal, properties, Options.AuthenticationType);
|
||||
if (!string.IsNullOrWhiteSpace(openIdConnectMessage.SessionState))
|
||||
{
|
||||
ticket.Properties.Dictionary[OpenIdConnectSessionProperties.SessionState] = openIdConnectMessage.SessionState;
|
||||
}
|
||||
|
||||
if (_configuration != null && !string.IsNullOrWhiteSpace(_configuration.CheckSessionIframe))
|
||||
{
|
||||
ticket.Properties.Dictionary[OpenIdConnectSessionProperties.CheckSessionIFrame] = _configuration.CheckSessionIframe;
|
||||
}
|
||||
|
||||
if (Options.UseTokenLifetime)
|
||||
{
|
||||
// Override any session persistence to match the token lifetime.
|
||||
DateTime issued = validatedToken.ValidFrom;
|
||||
if (issued != DateTime.MinValue)
|
||||
{
|
||||
ticket.Properties.IssuedUtc = issued;
|
||||
}
|
||||
|
||||
DateTime expires = validatedToken.ValidTo;
|
||||
if (expires != DateTime.MinValue)
|
||||
{
|
||||
ticket.Properties.ExpiresUtc = expires;
|
||||
}
|
||||
|
||||
ticket.Properties.AllowRefresh = false;
|
||||
}
|
||||
|
||||
var securityTokenValidatedNotification = new SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
|
||||
{
|
||||
AuthenticationTicket = ticket,
|
||||
ProtocolMessage = openIdConnectMessage
|
||||
};
|
||||
|
||||
await Options.Notifications.SecurityTokenValidated(securityTokenValidatedNotification);
|
||||
if (securityTokenValidatedNotification.HandledResponse)
|
||||
{
|
||||
return securityTokenValidatedNotification.AuthenticationTicket;
|
||||
}
|
||||
|
||||
if (securityTokenValidatedNotification.Skipped)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var protocolValidationContext = new OpenIdConnectProtocolValidationContext
|
||||
{
|
||||
AuthorizationCode = openIdConnectMessage.Code,
|
||||
Nonce = RetrieveNonce(jwt.Payload.Nonce),
|
||||
};
|
||||
|
||||
Options.ProtocolValidator.Validate(jwt, protocolValidationContext);
|
||||
if (openIdConnectMessage.Code != null)
|
||||
{
|
||||
var authorizationCodeReceivedNotification = new AuthorizationCodeReceivedNotification(Context, Options)
|
||||
{
|
||||
AuthenticationTicket = ticket,
|
||||
Code = openIdConnectMessage.Code,
|
||||
JwtSecurityToken = jwt,
|
||||
ProtocolMessage = openIdConnectMessage,
|
||||
RedirectUri = ticket.Properties.Dictionary.ContainsKey(OpenIdConnectAuthenticationDefaults.RedirectUriUsedForCodeKey) ?
|
||||
ticket.Properties.Dictionary[OpenIdConnectAuthenticationDefaults.RedirectUriUsedForCodeKey] : string.Empty,
|
||||
};
|
||||
|
||||
await Options.Notifications.AuthorizationCodeReceived(authorizationCodeReceivedNotification);
|
||||
if (authorizationCodeReceivedNotification.HandledResponse)
|
||||
{
|
||||
return authorizationCodeReceivedNotification.AuthenticationTicket;
|
||||
}
|
||||
|
||||
if (authorizationCodeReceivedNotification.Skipped)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return ticket;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
// We can't await inside a catch block, capture and handle outside.
|
||||
authFailedEx = ExceptionDispatchInfo.Capture(exception);
|
||||
}
|
||||
|
||||
if (authFailedEx != null)
|
||||
{
|
||||
_logger.WriteError("Exception occurred while processing message", authFailedEx.SourceException);
|
||||
|
||||
// Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the notification.
|
||||
if (Options.RefreshOnIssuerKeyNotFound && authFailedEx.SourceException.GetType().Equals(typeof(SecurityTokenSignatureKeyNotFoundException)))
|
||||
{
|
||||
Options.ConfigurationManager.RequestRefresh();
|
||||
}
|
||||
|
||||
var authenticationFailedNotification = new AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
|
||||
{
|
||||
ProtocolMessage = openIdConnectMessage,
|
||||
Exception = authFailedEx.SourceException
|
||||
};
|
||||
|
||||
await Options.Notifications.AuthenticationFailed(authenticationFailedNotification);
|
||||
if (authenticationFailedNotification.HandledResponse)
|
||||
{
|
||||
return authenticationFailedNotification.AuthenticationTicket;
|
||||
}
|
||||
|
||||
if (authenticationFailedNotification.Skipped)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
authFailedEx.Throw();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the nonce to <see cref="HttpResponse.Cookies"/>.
|
||||
/// </summary>
|
||||
/// <param name="nonce">the nonce to remember.</param>
|
||||
/// <remarks><see cref="HttpResponse.Cookies.Append"/>is called to add a cookie with the name: 'OpenIdConnectAuthenticationDefaults.Nonce + <see cref="OpenIdConnectAuthenticationOptions.StringDataFormat.Protect"/>(nonce)'.
|
||||
/// The value of the cookie is: "N".</remarks>
|
||||
private void RememberNonce(string nonce)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(nonce))
|
||||
{
|
||||
throw new ArgumentNullException("nonce");
|
||||
}
|
||||
|
||||
Response.Cookies.Append(
|
||||
OpenIdConnectAuthenticationDefaults.CookieNoncePrefix + Options.StringDataFormat.Protect(nonce),
|
||||
NonceProperty,
|
||||
new CookieOptions
|
||||
{
|
||||
HttpOnly = true,
|
||||
Secure = Request.IsSecure
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches <see cref="HttpRequest.Cookies"/> for a matching nonce.
|
||||
/// </summary>
|
||||
/// <param name="nonceExpectedValue">the nonce that was found in the jwt token.</param>
|
||||
/// <returns>'nonceExpectedValue' if a cookie is found that matches, null otherwise.</returns>
|
||||
/// <remarks>Examins <see cref="HttpRequest.Cookies.Keys"/> that start with the prefix: 'OpenIdConnectAuthenticationDefaults.Nonce'.
|
||||
/// <see cref="OpenIdConnectAuthenticationOptions.StringDataFormat.Unprotect"/> is used to obtain the actual 'nonce'. If the nonce is found, then <see cref="HttpResponse.Cookies.Delete"/> is called.</remarks>
|
||||
private string RetrieveNonce(string nonceExpectedValue)
|
||||
{
|
||||
if (nonceExpectedValue == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var nonceKey in Request.Cookies.Keys)
|
||||
{
|
||||
if (nonceKey.StartsWith(OpenIdConnectAuthenticationDefaults.CookieNoncePrefix))
|
||||
{
|
||||
try
|
||||
{
|
||||
string nonceDecodedValue = Options.StringDataFormat.Unprotect(nonceKey.Substring(OpenIdConnectAuthenticationDefaults.CookieNoncePrefix.Length, nonceKey.Length - OpenIdConnectAuthenticationDefaults.CookieNoncePrefix.Length));
|
||||
if (nonceDecodedValue == nonceExpectedValue)
|
||||
{
|
||||
var cookieOptions = new CookieOptions
|
||||
{
|
||||
HttpOnly = true,
|
||||
Secure = Request.IsSecure
|
||||
};
|
||||
|
||||
Response.Cookies.Delete(nonceKey, cookieOptions);
|
||||
return nonceExpectedValue;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.WriteWarning("Failed to un-protect the nonce cookie.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private AuthenticationProperties GetPropertiesFromState(string state)
|
||||
{
|
||||
// assume a well formed query string: <a=b&>OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey=kasjd;fljasldkjflksdj<&c=d>
|
||||
int startIndex = 0;
|
||||
if (string.IsNullOrWhiteSpace(state) || (startIndex = state.IndexOf(OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey, StringComparison.Ordinal)) == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int authenticationIndex = startIndex + OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey.Length;
|
||||
if (authenticationIndex == -1 || authenticationIndex == state.Length || state[authenticationIndex] != '=')
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// scan rest of string looking for '&'
|
||||
authenticationIndex++;
|
||||
int endIndex = state.Substring(authenticationIndex, state.Length - authenticationIndex).IndexOf("&", StringComparison.Ordinal);
|
||||
|
||||
// -1 => no other parameters are after the AuthenticationPropertiesKey
|
||||
if (endIndex == -1)
|
||||
{
|
||||
return Options.StateDataFormat.Unprotect(Uri.UnescapeDataString(state.Substring(authenticationIndex).Replace('+', ' ')));
|
||||
}
|
||||
else
|
||||
{
|
||||
return Options.StateDataFormat.Unprotect(Uri.UnescapeDataString(state.Substring(authenticationIndex, endIndex).Replace('+', ' ')));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls InvokeReplyPathAsync
|
||||
/// </summary>
|
||||
/// <returns>True if the request was handled, false if the next middleware should be invoked.</returns>
|
||||
public override Task<bool> InvokeAsync()
|
||||
{
|
||||
return InvokeReplyPathAsync();
|
||||
}
|
||||
|
||||
private async Task<bool> InvokeReplyPathAsync()
|
||||
{
|
||||
AuthenticationTicket ticket = await AuthenticateAsync();
|
||||
|
||||
if (ticket != null)
|
||||
{
|
||||
if (ticket.Principal != null)
|
||||
{
|
||||
Request.HttpContext.Response.SignIn(ticket.Properties, ticket.Principal.Identities);
|
||||
}
|
||||
|
||||
// Redirect back to the original secured resource, if any.
|
||||
if (!string.IsNullOrWhiteSpace(ticket.Properties.RedirectUri))
|
||||
{
|
||||
Response.Redirect(ticket.Properties.RedirectUri);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"version": "1.0.0-*",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Http.Extensions": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security.DataProtection": "1.0.0-*",
|
||||
"Microsoft.AspNet.WebUtilities": "1.0.0-*",
|
||||
"Microsoft.Framework.Logging": "1.0.0-*",
|
||||
"Newtonsoft.Json": "6.0.4",
|
||||
"Microsoft.IdentityModel.Protocol.Extensions": "2.0.0.0-beta1-*",
|
||||
"System.IdentityModel.Tokens": "5.0.0.0-beta1-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"aspnet50": {
|
||||
"frameworkAssemblies": {
|
||||
"System.Net.Http": "",
|
||||
"System.Net.Http.WebRequest": ""
|
||||
}
|
||||
},
|
||||
"aspnetcore50": {
|
||||
"dependencies": {
|
||||
"System.Collections": "4.0.10-beta-*",
|
||||
"System.ComponentModel": "4.0.0-beta-*",
|
||||
"System.Console": "4.0.0-beta-*",
|
||||
"System.Diagnostics.Debug": "4.0.10-beta-*",
|
||||
"System.Diagnostics.Tools": "4.0.0-beta-*",
|
||||
"System.Dynamic.Runtime": "4.0.0-beta-*",
|
||||
"System.Globalization": "4.0.10-beta-*",
|
||||
"System.IO": "4.0.10-beta-*",
|
||||
"System.IO.Compression": "4.0.0-beta-*",
|
||||
"System.Linq": "4.0.0-beta-*",
|
||||
"System.Net.Http.WinHttpHandler": "4.0.0-beta-*",
|
||||
"System.ObjectModel": "4.0.10-beta-*",
|
||||
"System.Reflection": "4.0.10-beta-*",
|
||||
"System.Resources.ResourceManager": "4.0.0-beta-*",
|
||||
"System.Runtime": "4.0.20-beta-*",
|
||||
"System.Runtime.Extensions": "4.0.10-beta-*",
|
||||
"System.Runtime.InteropServices": "4.0.20-beta-*",
|
||||
"System.Security.Claims": "4.0.0-beta-*",
|
||||
"System.Security.Cryptography.Hashing.Algorithms": "4.0.0-beta-*",
|
||||
"System.Security.Principal": "4.0.0-beta-*",
|
||||
"System.Threading": "4.0.0-beta-*",
|
||||
"System.Threading.Tasks": "4.0.10-beta-*",
|
||||
"System.Net.Http": "4.0.0-*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.34014
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.AspNet.Security.OpenIdConnect {
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Owin.Security.OpenIdConnect.Resources", IntrospectionExtensions.GetTypeInfo(typeof(Resources)).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to BackchannelTimeout cannot be less or equal to TimeSpan.Zero..
|
||||
/// </summary>
|
||||
internal static string ArgsException_BackchallelLessThanZero {
|
||||
get {
|
||||
return ResourceManager.GetString("ArgsException_BackchallelLessThanZero", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to "OpenIdConnectMessage.Error was not null, indicating an error. Error: '{0}'. Error_Description (may be empty): '{1}'. Error_Uri (may be empty): '{2}'.".
|
||||
/// </summary>
|
||||
internal static string Exception_OpenIdConnectMessageError {
|
||||
get {
|
||||
return ResourceManager.GetString("Exception_OpenIdConnectMessageError", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to OIDC_20001: The query string for Logout is not a well formed URI. The runtime cannot redirect. Redirect uri: '{0}'..
|
||||
/// </summary>
|
||||
internal static string Exception_RedirectUri_LogoutQueryString_IsNotWellFormed {
|
||||
get {
|
||||
return ResourceManager.GetString("Exception_RedirectUri_LogoutQueryString_IsNotWellFormed", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler..
|
||||
/// </summary>
|
||||
internal static string Exception_ValidatorHandlerMismatch {
|
||||
get {
|
||||
return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,8 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNet.Http.Security;
|
||||
using Microsoft.AspNet.HttpFeature.Security;
|
||||
using Microsoft.AspNet.PipelineCore.Security;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
|
|
@ -28,11 +22,34 @@ namespace Microsoft.AspNet.Security
|
|||
Properties = properties ?? new AuthenticationProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AuthenticationTicket"/> class
|
||||
/// </summary>
|
||||
/// <param name="identity">the <see cref="ClaimsPrincipal"/> that represents the authenticated user.</param>
|
||||
/// <param name="properties">additional properties that can be consumed by the user or runtims.</param>
|
||||
/// <param name="authenticationType">the authentication middleware that was responsible for this ticket.</param>
|
||||
public AuthenticationTicket(ClaimsPrincipal principal, AuthenticationProperties properties, string authenticationType)
|
||||
{
|
||||
AuthenticationType = authenticationType;
|
||||
Principal = principal;
|
||||
Properties = properties ?? new AuthenticationProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authenticated user identity.
|
||||
/// </summary>
|
||||
public string AuthenticationType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authenticated user identity.
|
||||
/// </summary>
|
||||
public ClaimsIdentity Identity { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authenticated user identity.
|
||||
/// </summary>
|
||||
public ClaimsPrincipal Principal{ get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional state values for the authentication session.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Security.Notifications;
|
||||
|
||||
|
|
@ -10,15 +8,11 @@ namespace Microsoft.AspNet.Security.Infrastructure
|
|||
{
|
||||
public class AuthenticationTokenReceiveContext : BaseContext
|
||||
{
|
||||
private readonly ISecureDataFormat<AuthenticationTicket> _secureDataFormat;
|
||||
|
||||
public AuthenticationTokenReceiveContext(
|
||||
[NotNull] HttpContext context,
|
||||
[NotNull] ISecureDataFormat<AuthenticationTicket> secureDataFormat,
|
||||
[NotNull] string token)
|
||||
: base(context)
|
||||
{
|
||||
_secureDataFormat = secureDataFormat;
|
||||
Token = token;
|
||||
}
|
||||
|
||||
|
|
@ -26,11 +20,6 @@ namespace Microsoft.AspNet.Security.Infrastructure
|
|||
|
||||
public AuthenticationTicket Ticket { get; protected set; }
|
||||
|
||||
public void DeserializeTicket(string protectedData)
|
||||
{
|
||||
Ticket = _secureDataFormat.Unprotect(protectedData);
|
||||
}
|
||||
|
||||
public void SetTicket([NotNull] AuthenticationTicket ticket)
|
||||
{
|
||||
Ticket = ticket;
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Notifications
|
||||
{
|
||||
public class AuthenticationFailedNotification<TMessage>
|
||||
public class AuthenticationFailedNotification<TMessage, TOptions> : BaseNotification<TOptions>
|
||||
{
|
||||
public AuthenticationFailedNotification()
|
||||
public AuthenticationFailedNotification(HttpContext context, TOptions options) : base(context, options)
|
||||
{
|
||||
}
|
||||
|
||||
public bool Cancel { get; set; }
|
||||
public Exception Exception { get; set; }
|
||||
|
||||
public TMessage ProtocolMessage { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Notifications
|
||||
{
|
||||
public class BaseNotification<TOptions> : BaseContext<TOptions>
|
||||
{
|
||||
protected BaseNotification(HttpContext context, TOptions options) : base(context, options)
|
||||
{
|
||||
}
|
||||
|
||||
public NotificationResultState State { get; set; }
|
||||
|
||||
public bool HandledResponse
|
||||
{
|
||||
get { return State == NotificationResultState.HandledResponse; }
|
||||
}
|
||||
|
||||
public bool Skipped
|
||||
{
|
||||
get { return State == NotificationResultState.Skipped; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discontinue all processing for this request and return to the client.
|
||||
/// The caller is responsible for generating the full response.
|
||||
/// Set the <see cref="AuthenticationTicket"/> to trigger SignIn.
|
||||
/// </summary>
|
||||
public void HandleResponse()
|
||||
{
|
||||
State = NotificationResultState.HandledResponse;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discontinue processing the request in the current middleware and pass control to the next one.
|
||||
/// SignIn will not be called.
|
||||
/// </summary>
|
||||
public void SkipToNextMiddleware()
|
||||
{
|
||||
State = NotificationResultState.Skipped;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or set the <see cref="AuthenticationTicket"/> to return if this notification signals it handled the notification.
|
||||
/// </summary>
|
||||
public AuthenticationTicket AuthenticationTicket { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Notifications
|
||||
{
|
||||
public class MessageReceivedNotification<TMessage>
|
||||
public class MessageReceivedNotification<TMessage, TOptions> : BaseNotification<TOptions>
|
||||
{
|
||||
public MessageReceivedNotification()
|
||||
public MessageReceivedNotification(HttpContext context, TOptions options) : base(context, options)
|
||||
{
|
||||
}
|
||||
|
||||
public bool Cancel { get; set; }
|
||||
public TMessage ProtocolMessage { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Notifications
|
||||
{
|
||||
public enum NotificationResultState
|
||||
{
|
||||
/// <summary>
|
||||
/// Continue with normal processing.
|
||||
/// </summary>
|
||||
Continue,
|
||||
|
||||
/// <summary>
|
||||
/// Discontinue processing the request in the current middleware and pass control to the next one.
|
||||
/// </summary>
|
||||
Skipped,
|
||||
|
||||
/// <summary>
|
||||
/// Discontinue all processing for this request.
|
||||
/// </summary>
|
||||
HandledResponse
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,21 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Notifications
|
||||
{
|
||||
public class RedirectFromIdentityProviderNotification
|
||||
public class RedirectFromIdentityProviderNotification<TMessage, TOptions> : BaseNotification<TOptions>
|
||||
{
|
||||
public AuthenticationTicket AuthenticationTicket { get; set; }
|
||||
public RedirectFromIdentityProviderNotification(HttpContext context, TOptions options)
|
||||
: base(context, options)
|
||||
{
|
||||
}
|
||||
|
||||
public string SignInAsAuthenticationType { get; set; }
|
||||
|
||||
public bool Cancel { get; set; }
|
||||
|
||||
public bool IsRequestCompleted { get; set; }
|
||||
|
||||
public TMessage ProtocolMessage { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Notifications
|
||||
{
|
||||
public class RedirectToIdentityProviderNotification<TMessage>
|
||||
public class RedirectToIdentityProviderNotification<TMessage, TOptions> : BaseNotification<TOptions>
|
||||
{
|
||||
public RedirectToIdentityProviderNotification()
|
||||
public RedirectToIdentityProviderNotification(HttpContext context, TOptions options) : base(context, options)
|
||||
{
|
||||
}
|
||||
|
||||
public bool Cancel { get; set; }
|
||||
public TMessage ProtocolMessage { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Notifications
|
||||
{
|
||||
public class SecurityTokenReceivedNotification
|
||||
public class SecurityTokenReceivedNotification<TMessage, TOptions> : BaseNotification<TOptions>
|
||||
{
|
||||
public SecurityTokenReceivedNotification()
|
||||
public SecurityTokenReceivedNotification(HttpContext context, TOptions options) : base(context, options)
|
||||
{
|
||||
}
|
||||
|
||||
public bool Cancel { get; set; }
|
||||
public string SecurityToken { get; set; }
|
||||
|
||||
public TMessage ProtocolMessage { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Notifications
|
||||
{
|
||||
public class SecurityTokenValidatedNotification
|
||||
public class SecurityTokenValidatedNotification<TMessage, TOptions> : BaseNotification<TOptions>
|
||||
{
|
||||
public SecurityTokenValidatedNotification()
|
||||
public SecurityTokenValidatedNotification(HttpContext context, TOptions options) : base(context, options)
|
||||
{
|
||||
}
|
||||
|
||||
public AuthenticationTicket AuthenticationTicket { get; set; }
|
||||
public bool Cancel { get; set; }
|
||||
public TMessage ProtocolMessage { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,237 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IdentityModel.Tokens;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Security.Notifications;
|
||||
using Microsoft.AspNet.TestHost;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuthBearer
|
||||
{
|
||||
public class OAuthBearerMiddlewareTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task BearerTokenValidation()
|
||||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.Authority = "https://login.windows.net/tushartest.onmicrosoft.com";
|
||||
options.Audience = "https://TusharTest.onmicrosoft.com/TodoListService-ManualJwt";
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateLifetime = false
|
||||
};
|
||||
});
|
||||
string newBearerToken = "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImtyaU1QZG1Cdng2OHNrVDgtbVBBQjNCc2VlQSJ9.eyJhdWQiOiJodHRwczovL1R1c2hhclRlc3Qub25taWNyb3NvZnQuY29tL1RvZG9MaXN0U2VydmljZS1NYW51YWxKd3QiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9hZmJlY2UwMy1hZWFhLTRmM2YtODVlNy1jZTA4ZGQyMGNlNTAvIiwiaWF0IjoxNDE4MzMwNjE0LCJuYmYiOjE0MTgzMzA2MTQsImV4cCI6MTQxODMzNDUxNCwidmVyIjoiMS4wIiwidGlkIjoiYWZiZWNlMDMtYWVhYS00ZjNmLTg1ZTctY2UwOGRkMjBjZTUwIiwiYW1yIjpbInB3ZCJdLCJvaWQiOiI1Mzk3OTdjMi00MDE5LTQ2NTktOWRiNS03MmM0Yzc3NzhhMzMiLCJ1cG4iOiJWaWN0b3JAVHVzaGFyVGVzdC5vbm1pY3Jvc29mdC5jb20iLCJ1bmlxdWVfbmFtZSI6IlZpY3RvckBUdXNoYXJUZXN0Lm9ubWljcm9zb2Z0LmNvbSIsInN1YiI6IkQyMm9aMW9VTzEzTUFiQXZrdnFyd2REVE80WXZJdjlzMV9GNWlVOVUwYnciLCJmYW1pbHlfbmFtZSI6Ikd1cHRhIiwiZ2l2ZW5fbmFtZSI6IlZpY3RvciIsImFwcGlkIjoiNjEzYjVhZjgtZjJjMy00MWI2LWExZGMtNDE2Yzk3ODAzMGI3IiwiYXBwaWRhY3IiOiIwIiwic2NwIjoidXNlcl9pbXBlcnNvbmF0aW9uIiwiYWNyIjoiMSJ9.N_Kw1EhoVGrHbE6hOcm7ERdZ7paBQiNdObvp2c6T6n5CE8p0fZqmUd-ya_EqwElcD6SiKSiP7gj0gpNUnOJcBl_H2X8GseaeeMxBrZdsnDL8qecc6_ygHruwlPltnLTdka67s1Ow4fDSHaqhVTEk6lzGmNEcbNAyb0CxQxU6o7Fh0yHRiWoLsT8yqYk8nKzsHXfZBNby4aRo3_hXaa4i0SZLYfDGGYPdttG4vT_u54QGGd4Wzbonv2gjDlllOVGOwoJS6kfl1h8mk0qxdiIaT_ChbDWgkWvTB7bTvBE-EgHgV0XmAo0WtJeSxgjsG3KhhEPsONmqrSjhIUV4IVnF2w";
|
||||
var response = await SendAsync(server, "http://example.com/oauth", newBearerToken);
|
||||
response.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CustomHeaderReceived()
|
||||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.Notifications.MessageReceived = HeaderReceived;
|
||||
});
|
||||
|
||||
var response = await SendAsync(server, "http://example.com/oauth", "someHeader someblob");
|
||||
response.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
private static Task HeaderReceived(MessageReceivedNotification<HttpContext, OAuthBearerAuthenticationOptions> notification)
|
||||
{
|
||||
List<Claim> claims =
|
||||
new List<Claim>
|
||||
{
|
||||
new Claim(ClaimTypes.Email, "bob@contoso.com"),
|
||||
new Claim(ClaimsIdentity.DefaultNameClaimType, "bob"),
|
||||
};
|
||||
|
||||
notification.AuthenticationTicket = new AuthenticationTicket(new ClaimsIdentity(claims, notification.Options.AuthenticationType), new Http.Security.AuthenticationProperties());
|
||||
notification.HandleResponse();
|
||||
|
||||
return Task.FromResult<object>(null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CustomTokenReceived()
|
||||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.Notifications.SecurityTokenReceived = SecurityTokenReceived;
|
||||
});
|
||||
|
||||
var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob");
|
||||
response.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
private static Task SecurityTokenReceived(SecurityTokenReceivedNotification<HttpContext, OAuthBearerAuthenticationOptions> notification)
|
||||
{
|
||||
List<Claim> claims =
|
||||
new List<Claim>
|
||||
{
|
||||
new Claim(ClaimTypes.Email, "bob@contoso.com"),
|
||||
new Claim(ClaimsIdentity.DefaultNameClaimType, "bob"),
|
||||
};
|
||||
|
||||
notification.AuthenticationTicket = new AuthenticationTicket(new ClaimsIdentity(claims, notification.Options.AuthenticationType), new Http.Security.AuthenticationProperties());
|
||||
notification.HandleResponse();
|
||||
|
||||
return Task.FromResult<object>(null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CustomTokenValidated()
|
||||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.Notifications.SecurityTokenValidated = SecurityTokenValidated;
|
||||
options.SecurityTokenValidators = new List<ISecurityTokenValidator>{new BlobTokenValidator(options.AuthenticationType)};
|
||||
});
|
||||
|
||||
var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob");
|
||||
response.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
private static Task SecurityTokenValidated(SecurityTokenValidatedNotification<HttpContext, OAuthBearerAuthenticationOptions> notification)
|
||||
{
|
||||
List<Claim> claims =
|
||||
new List<Claim>
|
||||
{
|
||||
new Claim(ClaimTypes.Email, "bob@contoso.com"),
|
||||
new Claim(ClaimsIdentity.DefaultNameClaimType, "bob"),
|
||||
};
|
||||
|
||||
notification.AuthenticationTicket = new AuthenticationTicket(new ClaimsIdentity(claims, notification.Options.AuthenticationType), new Http.Security.AuthenticationProperties());
|
||||
notification.HandleResponse();
|
||||
|
||||
return Task.FromResult<object>(null);
|
||||
}
|
||||
|
||||
class BlobTokenValidator : ISecurityTokenValidator
|
||||
{
|
||||
|
||||
public BlobTokenValidator(string authenticationType)
|
||||
{
|
||||
AuthenticationType = authenticationType;
|
||||
}
|
||||
|
||||
public string AuthenticationType { get; set; }
|
||||
|
||||
public bool CanValidateToken
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public int MaximumTokenSizeInBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
return 2*2*1024;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanReadToken(string securityToken)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
|
||||
{
|
||||
validatedToken = null;
|
||||
List<Claim> claims =
|
||||
new List<Claim>
|
||||
{
|
||||
new Claim(ClaimTypes.Email, "bob@contoso.com"),
|
||||
new Claim(ClaimsIdentity.DefaultNameClaimType, "bob"),
|
||||
};
|
||||
|
||||
return new ClaimsPrincipal(new ClaimsIdentity(claims, AuthenticationType));
|
||||
}
|
||||
}
|
||||
|
||||
private static TestServer CreateServer(Action<OAuthBearerAuthenticationOptions> configureOptions, Func<HttpContext, bool> handler = null)
|
||||
{
|
||||
return TestServer.Create(app =>
|
||||
{
|
||||
app.UseServices(services =>
|
||||
{
|
||||
services.AddDataProtection();
|
||||
});
|
||||
|
||||
if (configureOptions != null)
|
||||
{
|
||||
app.UseOAuthBearerAuthentication(configureOptions);
|
||||
}
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
var req = context.Request;
|
||||
var res = context.Response;
|
||||
if (req.Path == new PathString("/oauth"))
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
await next();
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task<Transaction> SendAsync(TestServer server, string uri, string authorizationHeader = null)
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, uri);
|
||||
if (!string.IsNullOrEmpty(authorizationHeader))
|
||||
{
|
||||
request.Headers.Add("Authorization", authorizationHeader);
|
||||
}
|
||||
|
||||
var transaction = new Transaction
|
||||
{
|
||||
Request = request,
|
||||
Response = await server.CreateClient().SendAsync(request),
|
||||
};
|
||||
|
||||
transaction.ResponseText = await transaction.Response.Content.ReadAsStringAsync();
|
||||
|
||||
if (transaction.Response.Content != null &&
|
||||
transaction.Response.Content.Headers.ContentType != null &&
|
||||
transaction.Response.Content.Headers.ContentType.MediaType == "text/xml")
|
||||
{
|
||||
transaction.ResponseElement = XElement.Parse(transaction.ResponseText);
|
||||
}
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
private class Transaction
|
||||
{
|
||||
public HttpRequestMessage Request { get; set; }
|
||||
public HttpResponseMessage Response { get; set; }
|
||||
public IList<string> SetCookie { get; set; }
|
||||
public string ResponseText { get; set; }
|
||||
public XElement ResponseElement { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,361 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Security;
|
||||
using Microsoft.AspNet.Security.Cookies;
|
||||
using Microsoft.AspNet.Security.DataHandler;
|
||||
using Microsoft.AspNet.Security.DataProtection;
|
||||
using Microsoft.AspNet.Security.OpenIdConnect;
|
||||
using Microsoft.AspNet.TestHost;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Newtonsoft.Json;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Tests.OpenIdConnect
|
||||
{
|
||||
public class OpenIdConnectMiddlewareTests
|
||||
{
|
||||
static string noncePrefix = "OpenIdConnect." + "Nonce.";
|
||||
static string nonceDelimiter = ".";
|
||||
|
||||
[Fact]
|
||||
public async Task ChallengeWillTriggerRedirect()
|
||||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.Authority = "https://login.windows.net/common";
|
||||
options.ClientId = "Test Id";
|
||||
options.SignInAsAuthenticationType = OpenIdConnectAuthenticationDefaults.AuthenticationType;
|
||||
});
|
||||
var transaction = await SendAsync(server, "https://example.com/challenge");
|
||||
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||
var location = transaction.Response.Headers.Location.ToString();
|
||||
location.ShouldContain("https://login.windows.net/common/oauth2/authorize?");
|
||||
location.ShouldContain("client_id=");
|
||||
location.ShouldContain("&response_type=");
|
||||
location.ShouldContain("&scope=");
|
||||
location.ShouldContain("&state=");
|
||||
location.ShouldContain("&response_mode=");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChallengeWillSetNonceCookie()
|
||||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.Authority = "https://login.windows.net/common";
|
||||
options.ClientId = "Test Id";
|
||||
});
|
||||
var transaction = await SendAsync(server, "https://example.com/challenge");
|
||||
transaction.SetCookie.Single().ShouldContain("OpenIdConnect.nonce.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChallengeWillSetDefaultScope()
|
||||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.Authority = "https://login.windows.net/common";
|
||||
options.ClientId = "Test Id";
|
||||
});
|
||||
var transaction = await SendAsync(server, "https://example.com/challenge");
|
||||
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||
transaction.Response.Headers.Location.Query.ShouldContain("&scope=" + Uri.EscapeDataString("openid profile"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChallengeWillUseOptionsProperties()
|
||||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.Authority = "https://login.windows.net/common";
|
||||
options.ClientId = "Test Id";
|
||||
options.SignInAsAuthenticationType = OpenIdConnectAuthenticationDefaults.AuthenticationType;
|
||||
options.Scope = "https://www.googleapis.com/auth/plus.login";
|
||||
options.ResponseType = "id_token";
|
||||
});
|
||||
var transaction = await SendAsync(server, "https://example.com/challenge");
|
||||
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||
var query = transaction.Response.Headers.Location.Query;
|
||||
query.ShouldContain("scope=" + Uri.EscapeDataString("https://www.googleapis.com/auth/plus.login"));
|
||||
query.ShouldContain("response_type=" + Uri.EscapeDataString("id_token"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChallengeWillUseNotifications()
|
||||
{
|
||||
ISecureDataFormat<AuthenticationProperties> stateFormat = new PropertiesDataFormat(new EphemeralDataProtectionProvider().CreateProtector("GoogleTest"));
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.Authority = "https://login.windows.net/common";
|
||||
options.ClientId = "Test Id";
|
||||
options.Notifications = new OpenIdConnectAuthenticationNotifications
|
||||
{
|
||||
MessageReceived = notification =>
|
||||
{
|
||||
notification.ProtocolMessage.Scope = "test openid profile";
|
||||
notification.HandleResponse();
|
||||
return Task.FromResult<object>(null);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var properties = new AuthenticationProperties();
|
||||
var state = stateFormat.Protect(properties);
|
||||
var transaction = await SendAsync(server,"https://example.com/challenge");
|
||||
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task SignOutWithDefaultRedirectUri()
|
||||
{
|
||||
ISecureDataFormat<AuthenticationProperties> stateFormat = new PropertiesDataFormat(new EphemeralDataProtectionProvider().CreateProtector("GoogleTest"));
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.Authority = "https://login.windows.net/common";
|
||||
options.ClientId = "Test Id";
|
||||
});
|
||||
|
||||
var transaction = await SendAsync(server, "https://example.com/signout");
|
||||
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||
transaction.Response.Headers.Location.AbsoluteUri.ShouldBe("https://login.windows.net/common/oauth2/logout");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SignOutWithCustomRedirectUri()
|
||||
{
|
||||
ISecureDataFormat<AuthenticationProperties> stateFormat = new PropertiesDataFormat(new EphemeralDataProtectionProvider().CreateProtector("GoogleTest"));
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.Authority = "https://login.windows.net/common";
|
||||
options.ClientId = "Test Id";
|
||||
options.PostLogoutRedirectUri = "https://example.com/logout";
|
||||
});
|
||||
|
||||
var transaction = await SendAsync(server, "https://example.com/signout");
|
||||
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||
transaction.Response.Headers.Location.AbsoluteUri.ShouldContain(Uri.EscapeDataString("https://example.com/logout"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
// Test Cases for calculating the expiration time of cookie from cookie name
|
||||
public void NonceCookieExpirationTime()
|
||||
{
|
||||
DateTime utcNow = DateTime.UtcNow;
|
||||
|
||||
GetNonceExpirationTime(noncePrefix + DateTime.MaxValue.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter, TimeSpan.FromHours(1)).ShouldBe(DateTime.MaxValue);
|
||||
|
||||
GetNonceExpirationTime(noncePrefix + DateTime.MinValue.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter, TimeSpan.FromHours(1)).ShouldBe(DateTime.MinValue + TimeSpan.FromHours(1));
|
||||
|
||||
GetNonceExpirationTime(noncePrefix + utcNow.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter, TimeSpan.FromHours(1)).ShouldBe(utcNow + TimeSpan.FromHours(1));
|
||||
|
||||
GetNonceExpirationTime(noncePrefix, TimeSpan.FromHours(1)).ShouldBe(DateTime.MinValue);
|
||||
|
||||
GetNonceExpirationTime("", TimeSpan.FromHours(1)).ShouldBe(DateTime.MinValue);
|
||||
|
||||
GetNonceExpirationTime(noncePrefix + noncePrefix, TimeSpan.FromHours(1)).ShouldBe(DateTime.MinValue);
|
||||
|
||||
GetNonceExpirationTime(noncePrefix + utcNow.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter + utcNow.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter, TimeSpan.FromHours(1)).ShouldBe(utcNow + TimeSpan.FromHours(1));
|
||||
|
||||
GetNonceExpirationTime(utcNow.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter + utcNow.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter, TimeSpan.FromHours(1)).ShouldBe(DateTime.MinValue);
|
||||
}
|
||||
|
||||
private static TestServer CreateServer(Action<OpenIdConnectAuthenticationOptions> configureOptions, Func<HttpContext, Task> handler = null)
|
||||
{
|
||||
return TestServer.Create(app =>
|
||||
{
|
||||
app.UseServices(services =>
|
||||
{
|
||||
services.AddDataProtection();
|
||||
services.Configure<ExternalAuthenticationOptions>(options =>
|
||||
{
|
||||
options.SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType;
|
||||
});
|
||||
});
|
||||
|
||||
app.UseCookieAuthentication(options =>
|
||||
{
|
||||
options.AuthenticationType = "OpenIdConnect";
|
||||
});
|
||||
app.UseOpenIdConnectAuthentication(configureOptions);
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
var req = context.Request;
|
||||
var res = context.Response;
|
||||
if (req.Path == new PathString("/challenge"))
|
||||
{
|
||||
res.Challenge("OpenIdConnect");
|
||||
res.StatusCode = 401;
|
||||
}
|
||||
else if (req.Path == new PathString("/signin"))
|
||||
{
|
||||
res.SignIn();
|
||||
}
|
||||
else if (req.Path == new PathString("/signout"))
|
||||
{
|
||||
res.SignOut(OpenIdConnectAuthenticationDefaults.AuthenticationType);
|
||||
}
|
||||
else if (handler != null)
|
||||
{
|
||||
await handler(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
await next();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task<Transaction> SendAsync(TestServer server, string uri, string cookieHeader = null)
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, uri);
|
||||
if (!string.IsNullOrEmpty(cookieHeader))
|
||||
{
|
||||
request.Headers.Add("Cookie", cookieHeader);
|
||||
}
|
||||
var transaction = new Transaction
|
||||
{
|
||||
Request = request,
|
||||
Response = await server.CreateClient().SendAsync(request),
|
||||
};
|
||||
if (transaction.Response.Headers.Contains("Set-Cookie"))
|
||||
{
|
||||
transaction.SetCookie = transaction.Response.Headers.GetValues("Set-Cookie").ToList();
|
||||
}
|
||||
transaction.ResponseText = await transaction.Response.Content.ReadAsStringAsync();
|
||||
|
||||
if (transaction.Response.Content != null &&
|
||||
transaction.Response.Content.Headers.ContentType != null &&
|
||||
transaction.Response.Content.Headers.ContentType.MediaType == "text/xml")
|
||||
{
|
||||
transaction.ResponseElement = XElement.Parse(transaction.ResponseText);
|
||||
}
|
||||
return transaction;
|
||||
}
|
||||
|
||||
private class Transaction
|
||||
{
|
||||
public HttpRequestMessage Request { get; set; }
|
||||
public HttpResponseMessage Response { get; set; }
|
||||
|
||||
public IList<string> SetCookie { get; set; }
|
||||
|
||||
public string ResponseText { get; set; }
|
||||
public XElement ResponseElement { get; set; }
|
||||
|
||||
public string AuthenticationCookieValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (SetCookie != null && SetCookie.Count > 0)
|
||||
{
|
||||
var authCookie = SetCookie.SingleOrDefault(c => c.Contains(".AspNet.Cookie="));
|
||||
if (authCookie != null)
|
||||
{
|
||||
return authCookie.Substring(0, authCookie.IndexOf(';'));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public string FindClaimValue(string claimType)
|
||||
{
|
||||
XElement claim = ResponseElement.Elements("claim").SingleOrDefault(elt => elt.Attribute("type").Value == claimType);
|
||||
if (claim == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return claim.Attribute("value").Value;
|
||||
}
|
||||
}
|
||||
private static void Describe(HttpResponse res, ClaimsIdentity identity)
|
||||
{
|
||||
res.StatusCode = 200;
|
||||
res.ContentType = "text/xml";
|
||||
var xml = new XElement("xml");
|
||||
if (identity != null)
|
||||
{
|
||||
xml.Add(identity.Claims.Select(claim => new XElement("claim", new XAttribute("type", claim.Type), new XAttribute("value", claim.Value))));
|
||||
}
|
||||
using (var memory = new MemoryStream())
|
||||
{
|
||||
using (var writer = new XmlTextWriter(memory, Encoding.UTF8))
|
||||
{
|
||||
xml.WriteTo(writer);
|
||||
}
|
||||
res.Body.Write(memory.ToArray(), 0, memory.ToArray().Length);
|
||||
}
|
||||
}
|
||||
|
||||
private class TestHttpMessageHandler : HttpMessageHandler
|
||||
{
|
||||
public Func<HttpRequestMessage, HttpResponseMessage> Sender { get; set; }
|
||||
|
||||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
|
||||
{
|
||||
if (Sender != null)
|
||||
{
|
||||
return Task.FromResult(Sender(request));
|
||||
}
|
||||
|
||||
return Task.FromResult<HttpResponseMessage>(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static HttpResponseMessage ReturnJsonResponse(object content)
|
||||
{
|
||||
var res = new HttpResponseMessage(HttpStatusCode.OK);
|
||||
var text = JsonConvert.SerializeObject(content);
|
||||
res.Content = new StringContent(text, Encoding.UTF8, "application/json");
|
||||
return res;
|
||||
}
|
||||
|
||||
private static DateTime GetNonceExpirationTime(string keyname, TimeSpan nonceLifetime)
|
||||
{
|
||||
DateTime nonceTime = DateTime.MinValue;
|
||||
string timestamp = null;
|
||||
int endOfTimestamp;
|
||||
if (keyname.StartsWith(noncePrefix, StringComparison.Ordinal))
|
||||
{
|
||||
timestamp = keyname.Substring(noncePrefix.Length);
|
||||
endOfTimestamp = timestamp.IndexOf('.');
|
||||
|
||||
if (endOfTimestamp != -1)
|
||||
{
|
||||
timestamp = timestamp.Substring(0, endOfTimestamp);
|
||||
try
|
||||
{
|
||||
nonceTime = DateTime.FromBinary(Convert.ToInt64(timestamp, CultureInfo.InvariantCulture));
|
||||
if ((nonceTime >= DateTime.UtcNow) && ((DateTime.MaxValue - nonceTime) < nonceLifetime))
|
||||
nonceTime = DateTime.MaxValue;
|
||||
else
|
||||
nonceTime += nonceLifetime;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
return nonceTime;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,8 @@
|
|||
"Microsoft.AspNet.Security.Facebook": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security.Google": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security.MicrosoftAccount": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security.OAuthBearer": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security.OpenIdConnect": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security.Twitter": "1.0.0-*",
|
||||
"Microsoft.AspNet.TestHost": "1.0.0-*",
|
||||
"Moq": "4.2.1312.1622",
|
||||
|
|
@ -18,7 +20,8 @@
|
|||
"frameworks": {
|
||||
"aspnet50": {
|
||||
"dependencies": {
|
||||
"Shouldly": "1.1.1.1"
|
||||
"Shouldly": "1.1.1.1",
|
||||
"System.Security.Claims": "1.0.0-*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue