diff --git a/samples/SocialSample/Startup.cs b/samples/SocialSample/Startup.cs index ffbaf8dc5..ed3f09a14 100644 --- a/samples/SocialSample/Startup.cs +++ b/samples/SocialSample/Startup.cs @@ -5,6 +5,7 @@ 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 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory) // 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 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory) AuthorizationEndpoint = GoogleDefaults.AuthorizationEndpoint, TokenEndpoint = GoogleDefaults.TokenEndpoint, Scope = { "openid", "profile", "email" }, - SaveTokensAsClaims = true + SaveTokens = true }); // See config.json @@ -146,27 +147,27 @@ [WebListener] Then you can choose to run the app as admin (see below) or add the 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 - }); - - //// 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" } - }); + //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" } + //}); // See config.json // https://github.com/settings/applications/ @@ -179,7 +180,7 @@ dnx web 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 @@ dnx web { await context.Response.WriteAsync(claim.Type + ": " + claim.Value + "
"); } + + await context.Response.WriteAsync("Tokens:
"); + + await context.Response.WriteAsync("Access Token: " + await context.Authentication.GetTokenAsync("access_token") + "
"); + await context.Response.WriteAsync("Refresh Token: " + await context.Authentication.GetTokenAsync("refresh_token") + "
"); + await context.Response.WriteAsync("Token Type: " + await context.Authentication.GetTokenAsync("token_type") + "
"); + await context.Response.WriteAsync("expires_at: " + await context.Authentication.GetTokenAsync("expires_at") + "
"); await context.Response.WriteAsync("Logout"); await context.Response.WriteAsync(""); }); diff --git a/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs b/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs index 00ecae0fb..613dfbc15 100644 --- a/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs @@ -153,6 +153,14 @@ protected override async Task HandleAuthenticateAsync() return AuthenticateResult.Skip(); } + if (Options.SaveToken) + { + ticket.Properties.StoreTokens(new[] + { + new AuthenticationToken { Name = "access_token", Value = token } + }); + } + return AuthenticateResult.Success(ticket); } } diff --git a/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerOptions.cs b/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerOptions.cs index 0a08ff3e4..a4b5ef2ae 100644 --- a/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerOptions.cs +++ b/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerOptions.cs @@ -110,5 +110,11 @@ public JwtBearerOptions() : base() /// Contains the types and definitions required for validating a token. /// if 'value' is null. public TokenValidationParameters TokenValidationParameters { get; set; } = new TokenValidationParameters(); + + /// + /// Defines whether the bearer token should be stored in the + /// after a successful authorization. + /// + public bool SaveToken { get; set; } = true; } } diff --git a/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthHandler.cs b/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthHandler.cs index 7a06bee70..ddd26d9f0 100644 --- a/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthHandler.cs @@ -85,21 +85,19 @@ protected override async Task HandleRemoteAuthenticateAsync( 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(); + 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 @@ protected override async Task HandleRemoteAuthenticateAsync( 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)); diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs index 4dc4a32b8..3c7f04438 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs @@ -105,9 +105,7 @@ protected override async Task HandleSignOutAsync(SignOutContext signout) 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 @@ protected override async Task HandleRemoteAuthenticateAsync( 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 @@ protected virtual async Task GetUserInformationAsync(OpenIdC /// /// The principal in which tokens are saved. /// The OpenID Connect response. - 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(); 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 @@ private void SaveTokens(ClaimsPrincipal principal, OpenIdConnectMessage message, 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) }); } } } diff --git a/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterHandler.cs b/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterHandler.cs index c39cce121..d856a9b84 100644 --- a/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterHandler.cs @@ -88,10 +88,12 @@ protected override async Task HandleRemoteAuthenticateAsync( }, 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)); diff --git a/src/Microsoft.AspNetCore.Authentication/AuthenticationToken.cs b/src/Microsoft.AspNetCore.Authentication/AuthenticationToken.cs new file mode 100644 index 000000000..8414d45cd --- /dev/null +++ b/src/Microsoft.AspNetCore.Authentication/AuthenticationToken.cs @@ -0,0 +1,11 @@ +// 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; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationOptions.cs b/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationOptions.cs index 0388c04bd..f1e359284 100644 --- a/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationOptions.cs +++ b/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationOptions.cs @@ -48,19 +48,19 @@ public string DisplayName set { Description.DisplayName = value; } } - /// - /// Defines whether access and refresh tokens should be stored in the - /// after a successful authorization with the remote provider. - /// This property is set to false by default to reduce - /// the size of the final authentication cookie. - /// - public bool SaveTokensAsClaims { get; set; } - /// /// Gets or sets the time limit for completing the authentication flow (15 minutes by default). /// public TimeSpan RemoteAuthenticationTimeout { get; set; } = TimeSpan.FromMinutes(15); public IRemoteAuthenticationEvents Events = new RemoteAuthenticationEvents(); + + /// + /// Defines whether access and refresh tokens should be stored in the + /// after a successful authorization. + /// This property is set to false by default to reduce + /// the size of the final authentication cookie. + /// + public bool SaveTokens { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Authentication/TokenExtensions.cs b/src/Microsoft.AspNetCore.Authentication/TokenExtensions.cs new file mode 100644 index 000000000..8065139ba --- /dev/null +++ b/src/Microsoft.AspNetCore.Authentication/TokenExtensions.cs @@ -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 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(); + 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 GetTokens(this AuthenticationProperties properties) + { + if (properties == null) + { + throw new ArgumentNullException(nameof(properties)); + } + + var tokens = new List(); + 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 GetTokenAsync(this AuthenticationManager manager, string tokenName) + { + return manager.GetTokenAsync(AuthenticationManager.AutomaticScheme, tokenName); + } + + public static async Task 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); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Authentication.Test/Google/GoogleMiddlewareTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/Google/GoogleMiddlewareTests.cs index 6a0780812..96bb574fb 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/Google/GoogleMiddlewareTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/Google/GoogleMiddlewareTests.cs @@ -292,6 +292,7 @@ public async Task ReplyPathWillAuthenticateValidAuthorizeCodeAndState(string cla { ClientId = "Test Id", ClientSecret = "Test Secret", + SaveTokens = true, StateDataFormat = stateFormat, ClaimsIssuer = claimsIssuer, BackchannelHttpHandler = new TestHttpMessageHandler @@ -334,6 +335,7 @@ public async Task ReplyPathWillAuthenticateValidAuthorizeCodeAndState(string cla } } }); + var properties = new AuthenticationProperties(); var correlationKey = ".xsrf"; var correlationValue = "TestCorrelationId"; @@ -361,6 +363,12 @@ public async Task ReplyPathWillAuthenticateValidAuthorizeCodeAndState(string cla // 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 @@ private static TestServer CreateServer(GoogleOptions options, Func 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); + } + } } diff --git a/test/Microsoft.AspNetCore.Authentication.Test/TokenExtensionTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/TokenExtensionTests.cs new file mode 100644 index 000000000..ef030d115 --- /dev/null +++ b/test/Microsoft.AspNetCore.Authentication.Test/TokenExtensionTests.cs @@ -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(); + 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(); + 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(); + 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()); + 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(); + 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(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")); + } + + } +} diff --git a/test/Microsoft.AspNetCore.Authentication.Test/Transaction.cs b/test/Microsoft.AspNetCore.Authentication.Test/Transaction.cs index 63f8af1bb..f7128a6f1 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/Transaction.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/Transaction.cs @@ -46,5 +46,17 @@ public string FindClaimValue(string claimType, string issuer = null) } 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; + } + } }