Save tokens in auth properties instead of claims

This commit is contained in:
Hao Kung 2016-03-03 12:50:46 -08:00
parent cedef4dcba
commit ace166fa31
13 changed files with 404 additions and 67 deletions

View File

@ -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>");
});

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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));

View File

@ -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) });
}
}
}

View File

@ -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));

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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);
}
}
}

View File

@ -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);

View File

@ -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);
}
}
}

View File

@ -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"));
}
}
}

View File

@ -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;
}
}
}