Save tokens in auth properties instead of claims
This commit is contained in:
parent
cedef4dcba
commit
ace166fa31
|
|
@ -5,6 +5,7 @@ using System.Net.Http.Headers;
|
|||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.Google;
|
||||
using Microsoft.AspNetCore.Authentication.MicrosoftAccount;
|
||||
|
|
@ -71,13 +72,13 @@ namespace CookieSample
|
|||
|
||||
// You must first create an app with facebook and add it's ID and Secret to your config.json or user-secrets.
|
||||
// https://developers.facebook.com/apps/
|
||||
app.UseFacebookAuthentication(new FacebookOptions
|
||||
{
|
||||
AppId = Configuration["facebook:appid"],
|
||||
AppSecret = Configuration["facebook:appsecret"],
|
||||
Scope = { "email" },
|
||||
Fields = { "name", "email" }
|
||||
});
|
||||
//app.UseFacebookAuthentication(new FacebookOptions
|
||||
//{
|
||||
// AppId = Configuration["facebook:appid"],
|
||||
// AppSecret = Configuration["facebook:appsecret"],
|
||||
// Scope = { "email" },
|
||||
// Fields = { "name", "email" }
|
||||
//});
|
||||
|
||||
// See config.json
|
||||
app.UseOAuthAuthentication(new OAuthOptions
|
||||
|
|
@ -90,7 +91,7 @@ namespace CookieSample
|
|||
AuthorizationEndpoint = GoogleDefaults.AuthorizationEndpoint,
|
||||
TokenEndpoint = GoogleDefaults.TokenEndpoint,
|
||||
Scope = { "openid", "profile", "email" },
|
||||
SaveTokensAsClaims = true
|
||||
SaveTokens = true
|
||||
});
|
||||
|
||||
// See config.json
|
||||
|
|
@ -146,27 +147,27 @@ namespace CookieSample
|
|||
The sample app can then be run via:
|
||||
dnx web
|
||||
*/
|
||||
app.UseOAuthAuthentication(new OAuthOptions
|
||||
{
|
||||
AuthenticationScheme = "Microsoft-AccessToken",
|
||||
DisplayName = "MicrosoftAccount-AccessToken - Requires project changes",
|
||||
ClientId = Configuration["msa:clientid"],
|
||||
ClientSecret = Configuration["msa:clientsecret"],
|
||||
CallbackPath = new PathString("/signin-microsoft-token"),
|
||||
AuthorizationEndpoint = MicrosoftAccountDefaults.AuthorizationEndpoint,
|
||||
TokenEndpoint = MicrosoftAccountDefaults.TokenEndpoint,
|
||||
Scope = { "wl.basic" },
|
||||
SaveTokensAsClaims = true
|
||||
});
|
||||
//app.UseOAuthAuthentication(new OAuthOptions
|
||||
//{
|
||||
// AuthenticationScheme = "Microsoft-AccessToken",
|
||||
// DisplayName = "MicrosoftAccount-AccessToken - Requires project changes",
|
||||
// ClientId = Configuration["msa:clientid"],
|
||||
// ClientSecret = Configuration["msa:clientsecret"],
|
||||
// CallbackPath = new PathString("/signin-microsoft-token"),
|
||||
// AuthorizationEndpoint = MicrosoftAccountDefaults.AuthorizationEndpoint,
|
||||
// TokenEndpoint = MicrosoftAccountDefaults.TokenEndpoint,
|
||||
// Scope = { "wl.basic" },
|
||||
// SaveTokens = true
|
||||
//});
|
||||
|
||||
//// You must first create an app with live.com and add it's ID and Secret to your config.json or user-secrets.
|
||||
app.UseMicrosoftAccountAuthentication(new MicrosoftAccountOptions
|
||||
{
|
||||
DisplayName = "MicrosoftAccount - Requires project changes",
|
||||
ClientId = Configuration["msa:clientid"],
|
||||
ClientSecret = Configuration["msa:clientsecret"],
|
||||
Scope = { "wl.emails" }
|
||||
});
|
||||
////// You must first create an app with live.com and add it's ID and Secret to your config.json or user-secrets.
|
||||
//app.UseMicrosoftAccountAuthentication(new MicrosoftAccountOptions
|
||||
//{
|
||||
// DisplayName = "MicrosoftAccount - Requires project changes",
|
||||
// ClientId = Configuration["msa:clientid"],
|
||||
// ClientSecret = Configuration["msa:clientsecret"],
|
||||
// Scope = { "wl.emails" }
|
||||
//});
|
||||
|
||||
// See config.json
|
||||
// https://github.com/settings/applications/
|
||||
|
|
@ -179,7 +180,7 @@ namespace CookieSample
|
|||
CallbackPath = new PathString("/signin-github-token"),
|
||||
AuthorizationEndpoint = "https://github.com/login/oauth/authorize",
|
||||
TokenEndpoint = "https://github.com/login/oauth/access_token",
|
||||
SaveTokensAsClaims = true
|
||||
SaveTokens = true
|
||||
});
|
||||
|
||||
// See config.json
|
||||
|
|
@ -318,6 +319,13 @@ namespace CookieSample
|
|||
{
|
||||
await context.Response.WriteAsync(claim.Type + ": " + claim.Value + "<br>");
|
||||
}
|
||||
|
||||
await context.Response.WriteAsync("Tokens:<br>");
|
||||
|
||||
await context.Response.WriteAsync("Access Token: " + await AuthenticationToken.GetTokenAsync(context, CookieAuthenticationDefaults.AuthenticationScheme, "access_token") + "<br>");
|
||||
await context.Response.WriteAsync("Refresh Token: " + await AuthenticationToken.GetTokenAsync(context, CookieAuthenticationDefaults.AuthenticationScheme, "refresh_token") + "<br>");
|
||||
await context.Response.WriteAsync("Token Type: " + await AuthenticationToken.GetTokenAsync(context, CookieAuthenticationDefaults.AuthenticationScheme, "token_type") + "<br>");
|
||||
await context.Response.WriteAsync("expires_at: " + await AuthenticationToken.GetTokenAsync(context, CookieAuthenticationDefaults.AuthenticationScheme, "expires_at") + "<br>");
|
||||
await context.Response.WriteAsync("<a href=\"/logout\">Logout</a>");
|
||||
await context.Response.WriteAsync("</body></html>");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -153,6 +153,14 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer
|
|||
return AuthenticateResult.Skip();
|
||||
}
|
||||
|
||||
if (Options.SaveToken)
|
||||
{
|
||||
ticket.Properties.StoreTokens(new[]
|
||||
{
|
||||
new AuthenticationToken { Name = "access_token", Value = token }
|
||||
});
|
||||
}
|
||||
|
||||
return AuthenticateResult.Success(ticket);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,5 +110,11 @@ namespace Microsoft.AspNetCore.Builder
|
|||
/// <remarks>Contains the types and definitions required for validating a token.</remarks>
|
||||
/// <exception cref="ArgumentNullException">if 'value' is null.</exception>
|
||||
public TokenValidationParameters TokenValidationParameters { get; set; } = new TokenValidationParameters();
|
||||
|
||||
/// <summary>
|
||||
/// Defines whether the bearer token should be stored in the
|
||||
/// <see cref="AuthenticationProperties"/> after a successful authorization.
|
||||
/// </summary>
|
||||
public bool SaveToken { get; set; } = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,21 +85,19 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
|
|||
|
||||
var identity = new ClaimsIdentity(Options.ClaimsIssuer);
|
||||
|
||||
if (Options.SaveTokensAsClaims)
|
||||
if (Options.SaveTokens)
|
||||
{
|
||||
identity.AddClaim(new Claim("access_token", tokens.AccessToken,
|
||||
ClaimValueTypes.String, Options.ClaimsIssuer));
|
||||
var authTokens = new List<AuthenticationToken>();
|
||||
|
||||
authTokens.Add(new AuthenticationToken { Name = "access_token", Value = tokens.AccessToken });
|
||||
if (!string.IsNullOrEmpty(tokens.RefreshToken))
|
||||
{
|
||||
identity.AddClaim(new Claim("refresh_token", tokens.RefreshToken,
|
||||
ClaimValueTypes.String, Options.ClaimsIssuer));
|
||||
authTokens.Add(new AuthenticationToken { Name = "refresh_token", Value = tokens.RefreshToken });
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(tokens.TokenType))
|
||||
{
|
||||
identity.AddClaim(new Claim("token_type", tokens.TokenType,
|
||||
ClaimValueTypes.String, Options.ClaimsIssuer));
|
||||
authTokens.Add(new AuthenticationToken { Name = "token_type", Value = tokens.TokenType });
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(tokens.ExpiresIn))
|
||||
|
|
@ -107,13 +105,18 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
|
|||
int value;
|
||||
if (int.TryParse(tokens.ExpiresIn, NumberStyles.Integer, CultureInfo.InvariantCulture, out value))
|
||||
{
|
||||
var expiresAt = Options.SystemClock.UtcNow + TimeSpan.FromSeconds(value);
|
||||
// https://www.w3.org/TR/xmlschema-2/#dateTime
|
||||
// https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx
|
||||
identity.AddClaim(new Claim("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture),
|
||||
ClaimValueTypes.DateTime, Options.ClaimsIssuer));
|
||||
var expiresAt = Options.SystemClock.UtcNow + TimeSpan.FromSeconds(value);
|
||||
authTokens.Add(new AuthenticationToken
|
||||
{
|
||||
Name = "expires_at",
|
||||
Value = expiresAt.ToString("o", CultureInfo.InvariantCulture)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
properties.StoreTokens(authTokens);
|
||||
}
|
||||
|
||||
return AuthenticateResult.Success(await CreateTicketAsync(identity, properties, tokens));
|
||||
|
|
|
|||
|
|
@ -105,9 +105,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
|||
message.PostLogoutRedirectUri = logoutRedirectUri;
|
||||
}
|
||||
|
||||
var principal = await Context.Authentication.AuthenticateAsync(Options.SignInScheme);
|
||||
message.IdTokenHint = principal?.FindFirst(OpenIdConnectParameterNames.IdToken)?.Value;
|
||||
|
||||
message.IdTokenHint = await Context.Authentication.GetTokenAsync(OpenIdConnectParameterNames.IdToken);
|
||||
var redirectContext = new RedirectContext(Context, Options, properties)
|
||||
{
|
||||
ProtocolMessage = message
|
||||
|
|
@ -513,9 +511,9 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
|||
tokenEndpointResponse = authenticationValidatedContext.TokenEndpointResponse;
|
||||
ticket = authenticationValidatedContext.Ticket;
|
||||
|
||||
if (Options.SaveTokensAsClaims)
|
||||
if (Options.SaveTokens)
|
||||
{
|
||||
SaveTokens(ticket.Principal, tokenEndpointResponse ?? authorizationResponse, jwt.Issuer);
|
||||
SaveTokens(ticket.Properties, tokenEndpointResponse ?? authorizationResponse);
|
||||
}
|
||||
|
||||
if (Options.GetClaimsFromUserInfoEndpoint)
|
||||
|
|
@ -693,32 +691,28 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
|||
/// </summary>
|
||||
/// <param name="principal">The principal in which tokens are saved.</param>
|
||||
/// <param name="message">The OpenID Connect response.</param>
|
||||
private void SaveTokens(ClaimsPrincipal principal, OpenIdConnectMessage message, string issuer)
|
||||
private void SaveTokens(AuthenticationProperties properties, OpenIdConnectMessage message)
|
||||
{
|
||||
var identity = (ClaimsIdentity)principal.Identity;
|
||||
var tokens = new List<AuthenticationToken>();
|
||||
|
||||
if (!string.IsNullOrEmpty(message.AccessToken))
|
||||
{
|
||||
identity.AddClaim(new Claim(OpenIdConnectParameterNames.AccessToken, message.AccessToken,
|
||||
ClaimValueTypes.String, issuer));
|
||||
tokens.Add(new AuthenticationToken { Name = OpenIdConnectParameterNames.AccessToken, Value = message.AccessToken });
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(message.IdToken))
|
||||
{
|
||||
identity.AddClaim(new Claim(OpenIdConnectParameterNames.IdToken, message.IdToken,
|
||||
ClaimValueTypes.String, issuer));
|
||||
tokens.Add(new AuthenticationToken { Name = OpenIdConnectParameterNames.IdToken, Value = message.IdToken });
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(message.RefreshToken))
|
||||
{
|
||||
identity.AddClaim(new Claim(OpenIdConnectParameterNames.RefreshToken, message.RefreshToken,
|
||||
ClaimValueTypes.String, issuer));
|
||||
tokens.Add(new AuthenticationToken { Name = OpenIdConnectParameterNames.RefreshToken, Value = message.RefreshToken });
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(message.TokenType))
|
||||
{
|
||||
identity.AddClaim(new Claim(OpenIdConnectParameterNames.TokenType, message.TokenType,
|
||||
ClaimValueTypes.String, issuer));
|
||||
tokens.Add(new AuthenticationToken { Name = OpenIdConnectParameterNames.TokenType, Value = message.TokenType });
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(message.ExpiresIn))
|
||||
|
|
@ -729,8 +723,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
|||
var expiresAt = Options.SystemClock.UtcNow + TimeSpan.FromSeconds(value);
|
||||
// https://www.w3.org/TR/xmlschema-2/#dateTime
|
||||
// https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx
|
||||
identity.AddClaim(new Claim("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture),
|
||||
ClaimValueTypes.DateTime, issuer));
|
||||
tokens.Add(new AuthenticationToken { Name = "expires_at", Value = expiresAt.ToString("o", CultureInfo.InvariantCulture) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,10 +88,12 @@ namespace Microsoft.AspNetCore.Authentication.Twitter
|
|||
},
|
||||
Options.ClaimsIssuer);
|
||||
|
||||
if (Options.SaveTokensAsClaims)
|
||||
if (Options.SaveTokens)
|
||||
{
|
||||
identity.AddClaim(new Claim("access_token", accessToken.Token, ClaimValueTypes.String, Options.ClaimsIssuer));
|
||||
identity.AddClaim(new Claim("access_token_secret", accessToken.TokenSecret, ClaimValueTypes.String, Options.ClaimsIssuer));
|
||||
properties.StoreTokens(new [] {
|
||||
new AuthenticationToken { Name = "access_token", Value = accessToken.Token },
|
||||
new AuthenticationToken { Name = "access_token_secret", Value = accessToken.TokenSecret }
|
||||
});
|
||||
}
|
||||
|
||||
return AuthenticateResult.Success(await CreateTicketAsync(identity, properties, accessToken));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
|
||||
namespace Microsoft.AspNetCore.Authentication
|
||||
{
|
||||
public class AuthenticationToken
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -51,19 +51,19 @@ namespace Microsoft.AspNetCore.Builder
|
|||
set { Description.DisplayName = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines whether access and refresh tokens should be stored in the
|
||||
/// <see cref="ClaimsPrincipal"/> after a successful authorization with the remote provider.
|
||||
/// This property is set to <c>false</c> by default to reduce
|
||||
/// the size of the final authentication cookie.
|
||||
/// </summary>
|
||||
public bool SaveTokensAsClaims { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the time limit for completing the authentication flow (15 minutes by default).
|
||||
/// </summary>
|
||||
public TimeSpan RemoteAuthenticationTimeout { get; set; } = TimeSpan.FromMinutes(15);
|
||||
|
||||
public IRemoteAuthenticationEvents Events = new RemoteAuthenticationEvents();
|
||||
|
||||
/// <summary>
|
||||
/// Defines whether access and refresh tokens should be stored in the
|
||||
/// <see cref="AuthenticationProperties"/> after a successful authorization.
|
||||
/// This property is set to <c>false</c> by default to reduce
|
||||
/// the size of the final authentication cookie.
|
||||
/// </summary>
|
||||
public bool SaveTokens { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
// Copyright (c) .NET Foundation. 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http.Authentication;
|
||||
using Microsoft.AspNetCore.Http.Features.Authentication;
|
||||
|
||||
namespace Microsoft.AspNetCore.Authentication
|
||||
{
|
||||
public static class AuthenticationTokenExtensions
|
||||
{
|
||||
private static string TokenNamesKey = ".TokenNames";
|
||||
private static string TokenKeyPrefix = ".Token.";
|
||||
|
||||
public static void StoreTokens(this AuthenticationProperties properties, IEnumerable<AuthenticationToken> tokens)
|
||||
{
|
||||
if (properties == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(properties));
|
||||
}
|
||||
if (tokens == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tokens));
|
||||
}
|
||||
|
||||
// Clear old tokens first
|
||||
var oldTokens = properties.GetTokens();
|
||||
foreach (var t in oldTokens)
|
||||
{
|
||||
properties.Items.Remove(TokenKeyPrefix + t.Name);
|
||||
}
|
||||
properties.Items.Remove(TokenNamesKey);
|
||||
|
||||
var tokenNames = new List<string>();
|
||||
foreach (var token in tokens)
|
||||
{
|
||||
// REVIEW: should probably check that there are no ; in the token name and throw or encode
|
||||
tokenNames.Add(token.Name);
|
||||
properties.Items[TokenKeyPrefix+token.Name] = token.Value;
|
||||
}
|
||||
if (tokenNames.Count > 0)
|
||||
{
|
||||
properties.Items[TokenNamesKey] = string.Join(";", tokenNames.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetTokenValue(this AuthenticationProperties properties, string tokenName)
|
||||
{
|
||||
if (properties == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(properties));
|
||||
}
|
||||
if (tokenName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tokenName));
|
||||
}
|
||||
|
||||
var tokenKey = TokenKeyPrefix + tokenName;
|
||||
return properties.Items.ContainsKey(tokenKey)
|
||||
? properties.Items[tokenKey]
|
||||
: null;
|
||||
}
|
||||
|
||||
public static IEnumerable<AuthenticationToken> GetTokens(this AuthenticationProperties properties)
|
||||
{
|
||||
if (properties == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(properties));
|
||||
}
|
||||
|
||||
var tokens = new List<AuthenticationToken>();
|
||||
if (properties.Items.ContainsKey(TokenNamesKey))
|
||||
{
|
||||
var tokenNames = properties.Items[TokenNamesKey].Split(';');
|
||||
foreach (var name in tokenNames)
|
||||
{
|
||||
var token = properties.GetTokenValue(name);
|
||||
if (token != null)
|
||||
{
|
||||
tokens.Add(new AuthenticationToken { Name = name, Value = token });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
public static Task<string> GetTokenAsync(this AuthenticationManager manager, string tokenName)
|
||||
{
|
||||
return manager.GetTokenAsync(AuthenticationManager.AutomaticScheme, tokenName);
|
||||
}
|
||||
|
||||
public static async Task<string> GetTokenAsync(this AuthenticationManager manager, string signInScheme, string tokenName)
|
||||
{
|
||||
if (manager == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(manager));
|
||||
}
|
||||
if (signInScheme == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(signInScheme));
|
||||
}
|
||||
if (tokenName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tokenName));
|
||||
}
|
||||
|
||||
var authContext = new AuthenticateContext(signInScheme);
|
||||
await manager.AuthenticateAsync(authContext);
|
||||
return new AuthenticationProperties(authContext.Properties).GetTokenValue(tokenName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -292,6 +292,7 @@ namespace Microsoft.AspNetCore.Authentication.Google
|
|||
{
|
||||
ClientId = "Test Id",
|
||||
ClientSecret = "Test Secret",
|
||||
SaveTokens = true,
|
||||
StateDataFormat = stateFormat,
|
||||
ClaimsIssuer = claimsIssuer,
|
||||
BackchannelHttpHandler = new TestHttpMessageHandler
|
||||
|
|
@ -334,6 +335,7 @@ namespace Microsoft.AspNetCore.Authentication.Google
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
var properties = new AuthenticationProperties();
|
||||
var correlationKey = ".xsrf";
|
||||
var correlationValue = "TestCorrelationId";
|
||||
|
|
@ -361,6 +363,12 @@ namespace Microsoft.AspNetCore.Authentication.Google
|
|||
|
||||
// Ensure claims transformation
|
||||
Assert.Equal("yup", transaction.FindClaimValue("xform"));
|
||||
|
||||
transaction = await server.SendAsync("https://example.com/tokens", authCookie);
|
||||
Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode);
|
||||
Assert.Equal("Test Access Token", transaction.FindTokenValue("access_token"));
|
||||
Assert.Equal("Bearer", transaction.FindTokenValue("token_type"));
|
||||
Assert.NotNull(transaction.FindTokenValue("expires_at"));
|
||||
}
|
||||
|
||||
// REVIEW: Fix this once we revisit error handling to not blow up
|
||||
|
|
@ -781,6 +789,13 @@ namespace Microsoft.AspNetCore.Authentication.Google
|
|||
{
|
||||
await context.Authentication.ChallengeAsync("Google");
|
||||
}
|
||||
else if (req.Path == new PathString("/tokens"))
|
||||
{
|
||||
var authContext = new AuthenticateContext(TestExtensions.CookieAuthenticationScheme);
|
||||
await context.Authentication.AuthenticateAsync(authContext);
|
||||
var tokens = AuthenticationToken.GetTokens(new AuthenticationProperties(authContext.Properties));
|
||||
res.Describe(tokens);
|
||||
}
|
||||
else if (req.Path == new PathString("/me"))
|
||||
{
|
||||
res.Describe(context.User);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
|
|
@ -63,5 +64,23 @@ namespace Microsoft.AspNetCore.Authentication
|
|||
var xmlBytes = Encoding.UTF8.GetBytes(xml.ToString());
|
||||
res.Body.Write(xmlBytes, 0, xmlBytes.Length);
|
||||
}
|
||||
|
||||
public static void Describe(this HttpResponse res, IEnumerable<AuthenticationToken> tokens)
|
||||
{
|
||||
res.StatusCode = 200;
|
||||
res.ContentType = "text/xml";
|
||||
var xml = new XElement("xml");
|
||||
if (tokens != null)
|
||||
{
|
||||
foreach (var token in tokens)
|
||||
{
|
||||
xml.Add(new XElement("token", new XAttribute("name", token.Name),
|
||||
new XAttribute("value", token.Value)));
|
||||
}
|
||||
}
|
||||
var xmlBytes = Encoding.UTF8.GetBytes(xml.ToString());
|
||||
res.Body.Write(xmlBytes, 0, xmlBytes.Length);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,144 @@
|
|||
// Copyright (c) .NET Foundation. 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;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http.Authentication;
|
||||
using Microsoft.AspNetCore.Http.Features.Authentication;
|
||||
using Microsoft.AspNetCore.Http.Features.Authentication.Internal;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Authentication
|
||||
{
|
||||
public class TokenExtensionTests
|
||||
{
|
||||
[Fact]
|
||||
public void CanStoreMultipleTokens()
|
||||
{
|
||||
var props = new AuthenticationProperties();
|
||||
var tokens = new List<AuthenticationToken>();
|
||||
var tok1 = new AuthenticationToken { Name = "One", Value = "1" };
|
||||
var tok2 = new AuthenticationToken { Name = "Two", Value = "2" };
|
||||
var tok3 = new AuthenticationToken { Name = "Three", Value = "3" };
|
||||
tokens.Add(tok1);
|
||||
tokens.Add(tok2);
|
||||
tokens.Add(tok3);
|
||||
props.StoreTokens(tokens);
|
||||
|
||||
Assert.Equal("1", props.GetTokenValue("One"));
|
||||
Assert.Equal("2", props.GetTokenValue("Two"));
|
||||
Assert.Equal("3", props.GetTokenValue("Three"));
|
||||
Assert.Equal(3, props.GetTokens().Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SubsequentStoreTokenDeletesPreviousTokens()
|
||||
{
|
||||
var props = new AuthenticationProperties();
|
||||
var tokens = new List<AuthenticationToken>();
|
||||
var tok1 = new AuthenticationToken { Name = "One", Value = "1" };
|
||||
var tok2 = new AuthenticationToken { Name = "Two", Value = "2" };
|
||||
var tok3 = new AuthenticationToken { Name = "Three", Value = "3" };
|
||||
tokens.Add(tok1);
|
||||
tokens.Add(tok2);
|
||||
tokens.Add(tok3);
|
||||
|
||||
props.StoreTokens(tokens);
|
||||
|
||||
props.StoreTokens(new[] { new AuthenticationToken { Name = "Zero", Value = "0" } });
|
||||
|
||||
Assert.Equal("0", props.GetTokenValue("Zero"));
|
||||
Assert.Equal(null, props.GetTokenValue("One"));
|
||||
Assert.Equal(null, props.GetTokenValue("Two"));
|
||||
Assert.Equal(null, props.GetTokenValue("Three"));
|
||||
Assert.Equal(1, props.GetTokens().Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanUpdateTokens()
|
||||
{
|
||||
var props = new AuthenticationProperties();
|
||||
var tokens = new List<AuthenticationToken>();
|
||||
var tok1 = new AuthenticationToken { Name = "One", Value = "1" };
|
||||
var tok2 = new AuthenticationToken { Name = "Two", Value = "2" };
|
||||
var tok3 = new AuthenticationToken { Name = "Three", Value = "3" };
|
||||
tokens.Add(tok1);
|
||||
tokens.Add(tok2);
|
||||
tokens.Add(tok3);
|
||||
props.StoreTokens(tokens);
|
||||
|
||||
tok1.Value = ".1";
|
||||
tok2.Value = ".2";
|
||||
tok3.Value = ".3";
|
||||
props.StoreTokens(tokens);
|
||||
|
||||
Assert.Equal(".1", props.GetTokenValue("One"));
|
||||
Assert.Equal(".2", props.GetTokenValue("Two"));
|
||||
Assert.Equal(".3", props.GetTokenValue("Three"));
|
||||
Assert.Equal(3, props.GetTokens().Count());
|
||||
}
|
||||
|
||||
public class TestAuthHandler : IAuthenticationHandler
|
||||
{
|
||||
private readonly AuthenticationProperties _props;
|
||||
public TestAuthHandler(AuthenticationProperties props)
|
||||
{
|
||||
_props = props;
|
||||
}
|
||||
|
||||
public Task AuthenticateAsync(AuthenticateContext context)
|
||||
{
|
||||
context.Authenticated(new ClaimsPrincipal(), _props.Items, new Dictionary<string, object>());
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task ChallengeAsync(ChallengeContext context)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void GetDescriptions(DescribeSchemesContext context)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task SignInAsync(SignInContext context)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task SignOutAsync(SignOutContext context)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanGetTokenFromContext()
|
||||
{
|
||||
var props = new AuthenticationProperties();
|
||||
var tokens = new List<AuthenticationToken>();
|
||||
var tok1 = new AuthenticationToken { Name = "One", Value = "1" };
|
||||
var tok2 = new AuthenticationToken { Name = "Two", Value = "2" };
|
||||
var tok3 = new AuthenticationToken { Name = "Three", Value = "3" };
|
||||
tokens.Add(tok1);
|
||||
tokens.Add(tok2);
|
||||
tokens.Add(tok3);
|
||||
props.StoreTokens(tokens);
|
||||
|
||||
var context = new DefaultHttpContext();
|
||||
var handler = new TestAuthHandler(props);
|
||||
context.Features.Set<IHttpAuthenticationFeature>(new HttpAuthenticationFeature() { Handler = handler });
|
||||
|
||||
Assert.Equal("1", await context.Authentication.GetTokenAsync("One"));
|
||||
Assert.Equal("2", await context.Authentication.GetTokenAsync("Two"));
|
||||
Assert.Equal("3", await context.Authentication.GetTokenAsync("Three"));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -46,5 +46,17 @@ namespace Microsoft.AspNetCore.Authentication
|
|||
}
|
||||
return claim.Attribute("value").Value;
|
||||
}
|
||||
|
||||
public string FindTokenValue(string name)
|
||||
{
|
||||
var claim = ResponseElement.Elements("token")
|
||||
.SingleOrDefault(elt => elt.Attribute("name").Value == name);
|
||||
if (claim == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return claim.Attribute("value").Value;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue