Additions for OpenIdConnectMiddleware and OAuthBearer Beta1.
This commit is contained in:
parent
fbe80ee64e
commit
49e66f0311
|
|
@ -3,5 +3,6 @@
|
||||||
<packageSources>
|
<packageSources>
|
||||||
<add key="AspNetVNext" value="https://www.myget.org/F/aspnetvnext/api/v2" />
|
<add key="AspNetVNext" value="https://www.myget.org/F/aspnetvnext/api/v2" />
|
||||||
<add key="NuGet.org" value="https://nuget.org/api/v2/" />
|
<add key="NuGet.org" value="https://nuget.org/api/v2/" />
|
||||||
|
<add key="AzureADNighty" value="http://www.myget.org/F/azureadwebstacknightly"/>
|
||||||
</packageSources>
|
</packageSources>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
||||||
47
Security.sln
47
Security.sln
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 14
|
# Visual Studio 14
|
||||||
VisualStudioVersion = 14.0.22013.1
|
VisualStudioVersion = 14.0.22422.0
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4D2B6A51-2F9F-44F5-8131-EA5CAC053652}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4D2B6A51-2F9F-44F5-8131-EA5CAC053652}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|
@ -36,6 +36,12 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Security.O
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CookieSessionSample", "samples\CookieSessionSample\CookieSessionSample.kproj", "{19711880-46DA-4A26-9E0F-9B2E41D27651}"
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CookieSessionSample", "samples\CookieSessionSample\CookieSessionSample.kproj", "{19711880-46DA-4A26-9E0F-9B2E41D27651}"
|
||||||
EndProject
|
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
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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.ActiveCfg = Release|Any CPU
|
||||||
{19711880-46DA-4A26-9E0F-9B2E41D27651}.Release|Mixed Platforms.Build.0 = 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
|
{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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -172,5 +214,8 @@ Global
|
||||||
{1FCF26C2-A3C7-4308-B698-4AFC3560BC0C} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
|
{1FCF26C2-A3C7-4308-B698-4AFC3560BC0C} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
|
||||||
{4A636011-68EE-4CE5-836D-EA8E13CF71E4} = {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}
|
{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
|
EndGlobalSection
|
||||||
EndGlobal
|
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.
|
// 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.
|
// 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.Builder;
|
||||||
using Microsoft.AspNet.Security.DataHandler;
|
using Microsoft.AspNet.Security.DataHandler;
|
||||||
using Microsoft.AspNet.Security.DataProtection;
|
using Microsoft.AspNet.Security.DataProtection;
|
||||||
using Microsoft.AspNet.Security.Infrastructure;
|
using Microsoft.AspNet.Security.Infrastructure;
|
||||||
using Microsoft.Framework.Logging;
|
using Microsoft.Framework.Logging;
|
||||||
using Microsoft.Framework.OptionsModel;
|
using Microsoft.Framework.OptionsModel;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Security.OAuth
|
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"));
|
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "AuthenticationType"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(Options.ClientId))
|
if (string.IsNullOrWhiteSpace(Options.ClientId))
|
||||||
{
|
{
|
||||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientId"));
|
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientId"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(Options.ClientSecret))
|
if (string.IsNullOrWhiteSpace(Options.ClientSecret))
|
||||||
{
|
{
|
||||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret"));
|
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(Options.AuthorizationEndpoint))
|
if (string.IsNullOrWhiteSpace(Options.AuthorizationEndpoint))
|
||||||
{
|
{
|
||||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "AuthorizationEndpoint"));
|
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "AuthorizationEndpoint"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(Options.TokenEndpoint))
|
if (string.IsNullOrWhiteSpace(Options.TokenEndpoint))
|
||||||
{
|
{
|
||||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "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.",
|
"description": "ASP.NET 5 middleware that enables an application to support any standard OAuth 2.0 authentication workflow.",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.AspNet.Security": "1.0.0-*",
|
"Microsoft.AspNet.Security": "1.0.0-*",
|
||||||
"Microsoft.AspNet.Security.DataProtection": "1.0.0-*"
|
"Microsoft.AspNet.Security.DataProtection": "1.0.0-*",
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"aspnet50": {
|
"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.
|
// 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.
|
// 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>
|
/// <summary>
|
||||||
/// Default values used by authorization server and bearer authentication.
|
/// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.AspNet.Security.OAuth;
|
using Microsoft.AspNet.Security.OAuthBearer;
|
||||||
using Microsoft.Framework.DependencyInjection;
|
using Microsoft.Framework.DependencyInjection;
|
||||||
using Microsoft.Framework.OptionsModel;
|
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.
|
// 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.
|
// 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 System.Security.Claims;
|
||||||
using Microsoft.AspNet.Http.Security;
|
using Microsoft.AspNet.Http.Security;
|
||||||
using Microsoft.AspNet.HttpFeature.Security;
|
|
||||||
using Microsoft.AspNet.PipelineCore.Security;
|
|
||||||
using Microsoft.AspNet.Security.Infrastructure;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Security
|
namespace Microsoft.AspNet.Security
|
||||||
{
|
{
|
||||||
|
|
@ -28,11 +22,34 @@ namespace Microsoft.AspNet.Security
|
||||||
Properties = properties ?? new AuthenticationProperties();
|
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>
|
/// <summary>
|
||||||
/// Gets the authenticated user identity.
|
/// Gets the authenticated user identity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ClaimsIdentity Identity { get; private set; }
|
public ClaimsIdentity Identity { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the authenticated user identity.
|
||||||
|
/// </summary>
|
||||||
|
public ClaimsPrincipal Principal{ get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Additional state values for the authentication session.
|
/// Additional state values for the authentication session.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// 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.
|
// 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.Http;
|
||||||
using Microsoft.AspNet.Security.Notifications;
|
using Microsoft.AspNet.Security.Notifications;
|
||||||
|
|
||||||
|
|
@ -10,15 +8,11 @@ namespace Microsoft.AspNet.Security.Infrastructure
|
||||||
{
|
{
|
||||||
public class AuthenticationTokenReceiveContext : BaseContext
|
public class AuthenticationTokenReceiveContext : BaseContext
|
||||||
{
|
{
|
||||||
private readonly ISecureDataFormat<AuthenticationTicket> _secureDataFormat;
|
|
||||||
|
|
||||||
public AuthenticationTokenReceiveContext(
|
public AuthenticationTokenReceiveContext(
|
||||||
[NotNull] HttpContext context,
|
[NotNull] HttpContext context,
|
||||||
[NotNull] ISecureDataFormat<AuthenticationTicket> secureDataFormat,
|
|
||||||
[NotNull] string token)
|
[NotNull] string token)
|
||||||
: base(context)
|
: base(context)
|
||||||
{
|
{
|
||||||
_secureDataFormat = secureDataFormat;
|
|
||||||
Token = token;
|
Token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,11 +20,6 @@ namespace Microsoft.AspNet.Security.Infrastructure
|
||||||
|
|
||||||
public AuthenticationTicket Ticket { get; protected set; }
|
public AuthenticationTicket Ticket { get; protected set; }
|
||||||
|
|
||||||
public void DeserializeTicket(string protectedData)
|
|
||||||
{
|
|
||||||
Ticket = _secureDataFormat.Unprotect(protectedData);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetTicket([NotNull] AuthenticationTicket ticket)
|
public void SetTicket([NotNull] AuthenticationTicket ticket)
|
||||||
{
|
{
|
||||||
Ticket = ticket;
|
Ticket = ticket;
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Security.Notifications
|
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 Exception Exception { get; set; }
|
||||||
|
|
||||||
public TMessage ProtocolMessage { 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.
|
// 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.
|
// 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
|
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; }
|
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.
|
// 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.
|
// 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
|
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 string SignInAsAuthenticationType { get; set; }
|
||||||
|
|
||||||
public bool Cancel { get; set; }
|
|
||||||
|
|
||||||
public bool IsRequestCompleted { 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.
|
// 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.
|
// 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
|
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; }
|
public TMessage ProtocolMessage { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,18 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// 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.
|
// 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
|
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 string SecurityToken { get; set; }
|
||||||
|
|
||||||
|
public TMessage ProtocolMessage { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// 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.
|
// 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
|
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 TMessage ProtocolMessage { get; set; }
|
||||||
public bool Cancel { 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.Facebook": "1.0.0-*",
|
||||||
"Microsoft.AspNet.Security.Google": "1.0.0-*",
|
"Microsoft.AspNet.Security.Google": "1.0.0-*",
|
||||||
"Microsoft.AspNet.Security.MicrosoftAccount": "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.Security.Twitter": "1.0.0-*",
|
||||||
"Microsoft.AspNet.TestHost": "1.0.0-*",
|
"Microsoft.AspNet.TestHost": "1.0.0-*",
|
||||||
"Moq": "4.2.1312.1622",
|
"Moq": "4.2.1312.1622",
|
||||||
|
|
@ -18,7 +20,8 @@
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"aspnet50": {
|
"aspnet50": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Shouldly": "1.1.1.1"
|
"Shouldly": "1.1.1.1",
|
||||||
|
"System.Security.Claims": "1.0.0-*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue