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.Security.Claims;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using Microsoft.AspNetCore.Authentication.Google;
|
using Microsoft.AspNetCore.Authentication.Google;
|
||||||
using Microsoft.AspNetCore.Authentication.MicrosoftAccount;
|
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.
|
// 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/
|
// https://developers.facebook.com/apps/
|
||||||
app.UseFacebookAuthentication(new FacebookOptions
|
//app.UseFacebookAuthentication(new FacebookOptions
|
||||||
{
|
//{
|
||||||
AppId = Configuration["facebook:appid"],
|
// AppId = Configuration["facebook:appid"],
|
||||||
AppSecret = Configuration["facebook:appsecret"],
|
// AppSecret = Configuration["facebook:appsecret"],
|
||||||
Scope = { "email" },
|
// Scope = { "email" },
|
||||||
Fields = { "name", "email" }
|
// Fields = { "name", "email" }
|
||||||
});
|
//});
|
||||||
|
|
||||||
// See config.json
|
// See config.json
|
||||||
app.UseOAuthAuthentication(new OAuthOptions
|
app.UseOAuthAuthentication(new OAuthOptions
|
||||||
|
|
@ -90,7 +91,7 @@ namespace CookieSample
|
||||||
AuthorizationEndpoint = GoogleDefaults.AuthorizationEndpoint,
|
AuthorizationEndpoint = GoogleDefaults.AuthorizationEndpoint,
|
||||||
TokenEndpoint = GoogleDefaults.TokenEndpoint,
|
TokenEndpoint = GoogleDefaults.TokenEndpoint,
|
||||||
Scope = { "openid", "profile", "email" },
|
Scope = { "openid", "profile", "email" },
|
||||||
SaveTokensAsClaims = true
|
SaveTokens = true
|
||||||
});
|
});
|
||||||
|
|
||||||
// See config.json
|
// See config.json
|
||||||
|
|
@ -146,27 +147,27 @@ namespace CookieSample
|
||||||
The sample app can then be run via:
|
The sample app can then be run via:
|
||||||
dnx web
|
dnx web
|
||||||
*/
|
*/
|
||||||
app.UseOAuthAuthentication(new OAuthOptions
|
//app.UseOAuthAuthentication(new OAuthOptions
|
||||||
{
|
//{
|
||||||
AuthenticationScheme = "Microsoft-AccessToken",
|
// AuthenticationScheme = "Microsoft-AccessToken",
|
||||||
DisplayName = "MicrosoftAccount-AccessToken - Requires project changes",
|
// DisplayName = "MicrosoftAccount-AccessToken - Requires project changes",
|
||||||
ClientId = Configuration["msa:clientid"],
|
// ClientId = Configuration["msa:clientid"],
|
||||||
ClientSecret = Configuration["msa:clientsecret"],
|
// ClientSecret = Configuration["msa:clientsecret"],
|
||||||
CallbackPath = new PathString("/signin-microsoft-token"),
|
// CallbackPath = new PathString("/signin-microsoft-token"),
|
||||||
AuthorizationEndpoint = MicrosoftAccountDefaults.AuthorizationEndpoint,
|
// AuthorizationEndpoint = MicrosoftAccountDefaults.AuthorizationEndpoint,
|
||||||
TokenEndpoint = MicrosoftAccountDefaults.TokenEndpoint,
|
// TokenEndpoint = MicrosoftAccountDefaults.TokenEndpoint,
|
||||||
Scope = { "wl.basic" },
|
// Scope = { "wl.basic" },
|
||||||
SaveTokensAsClaims = true
|
// 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.
|
////// 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
|
//app.UseMicrosoftAccountAuthentication(new MicrosoftAccountOptions
|
||||||
{
|
//{
|
||||||
DisplayName = "MicrosoftAccount - Requires project changes",
|
// DisplayName = "MicrosoftAccount - Requires project changes",
|
||||||
ClientId = Configuration["msa:clientid"],
|
// ClientId = Configuration["msa:clientid"],
|
||||||
ClientSecret = Configuration["msa:clientsecret"],
|
// ClientSecret = Configuration["msa:clientsecret"],
|
||||||
Scope = { "wl.emails" }
|
// Scope = { "wl.emails" }
|
||||||
});
|
//});
|
||||||
|
|
||||||
// See config.json
|
// See config.json
|
||||||
// https://github.com/settings/applications/
|
// https://github.com/settings/applications/
|
||||||
|
|
@ -179,7 +180,7 @@ namespace CookieSample
|
||||||
CallbackPath = new PathString("/signin-github-token"),
|
CallbackPath = new PathString("/signin-github-token"),
|
||||||
AuthorizationEndpoint = "https://github.com/login/oauth/authorize",
|
AuthorizationEndpoint = "https://github.com/login/oauth/authorize",
|
||||||
TokenEndpoint = "https://github.com/login/oauth/access_token",
|
TokenEndpoint = "https://github.com/login/oauth/access_token",
|
||||||
SaveTokensAsClaims = true
|
SaveTokens = true
|
||||||
});
|
});
|
||||||
|
|
||||||
// See config.json
|
// See config.json
|
||||||
|
|
@ -318,6 +319,13 @@ namespace CookieSample
|
||||||
{
|
{
|
||||||
await context.Response.WriteAsync(claim.Type + ": " + claim.Value + "<br>");
|
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("<a href=\"/logout\">Logout</a>");
|
||||||
await context.Response.WriteAsync("</body></html>");
|
await context.Response.WriteAsync("</body></html>");
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,14 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer
|
||||||
return AuthenticateResult.Skip();
|
return AuthenticateResult.Skip();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Options.SaveToken)
|
||||||
|
{
|
||||||
|
ticket.Properties.StoreTokens(new[]
|
||||||
|
{
|
||||||
|
new AuthenticationToken { Name = "access_token", Value = token }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return AuthenticateResult.Success(ticket);
|
return AuthenticateResult.Success(ticket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -110,5 +110,11 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
/// <remarks>Contains the types and definitions required for validating a token.</remarks>
|
/// <remarks>Contains the types and definitions required for validating a token.</remarks>
|
||||||
/// <exception cref="ArgumentNullException">if 'value' is null.</exception>
|
/// <exception cref="ArgumentNullException">if 'value' is null.</exception>
|
||||||
public TokenValidationParameters TokenValidationParameters { get; set; } = new TokenValidationParameters();
|
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);
|
var identity = new ClaimsIdentity(Options.ClaimsIssuer);
|
||||||
|
|
||||||
if (Options.SaveTokensAsClaims)
|
if (Options.SaveTokens)
|
||||||
{
|
{
|
||||||
identity.AddClaim(new Claim("access_token", tokens.AccessToken,
|
var authTokens = new List<AuthenticationToken>();
|
||||||
ClaimValueTypes.String, Options.ClaimsIssuer));
|
|
||||||
|
|
||||||
|
authTokens.Add(new AuthenticationToken { Name = "access_token", Value = tokens.AccessToken });
|
||||||
if (!string.IsNullOrEmpty(tokens.RefreshToken))
|
if (!string.IsNullOrEmpty(tokens.RefreshToken))
|
||||||
{
|
{
|
||||||
identity.AddClaim(new Claim("refresh_token", tokens.RefreshToken,
|
authTokens.Add(new AuthenticationToken { Name = "refresh_token", Value = tokens.RefreshToken });
|
||||||
ClaimValueTypes.String, Options.ClaimsIssuer));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(tokens.TokenType))
|
if (!string.IsNullOrEmpty(tokens.TokenType))
|
||||||
{
|
{
|
||||||
identity.AddClaim(new Claim("token_type", tokens.TokenType,
|
authTokens.Add(new AuthenticationToken { Name = "token_type", Value = tokens.TokenType });
|
||||||
ClaimValueTypes.String, Options.ClaimsIssuer));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(tokens.ExpiresIn))
|
if (!string.IsNullOrEmpty(tokens.ExpiresIn))
|
||||||
|
|
@ -107,13 +105,18 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
|
||||||
int value;
|
int value;
|
||||||
if (int.TryParse(tokens.ExpiresIn, NumberStyles.Integer, CultureInfo.InvariantCulture, out 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://www.w3.org/TR/xmlschema-2/#dateTime
|
||||||
// https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx
|
// https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx
|
||||||
identity.AddClaim(new Claim("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture),
|
var expiresAt = Options.SystemClock.UtcNow + TimeSpan.FromSeconds(value);
|
||||||
ClaimValueTypes.DateTime, Options.ClaimsIssuer));
|
authTokens.Add(new AuthenticationToken
|
||||||
|
{
|
||||||
|
Name = "expires_at",
|
||||||
|
Value = expiresAt.ToString("o", CultureInfo.InvariantCulture)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
properties.StoreTokens(authTokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
return AuthenticateResult.Success(await CreateTicketAsync(identity, properties, tokens));
|
return AuthenticateResult.Success(await CreateTicketAsync(identity, properties, tokens));
|
||||||
|
|
|
||||||
|
|
@ -105,9 +105,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
||||||
message.PostLogoutRedirectUri = logoutRedirectUri;
|
message.PostLogoutRedirectUri = logoutRedirectUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
var principal = await Context.Authentication.AuthenticateAsync(Options.SignInScheme);
|
message.IdTokenHint = await Context.Authentication.GetTokenAsync(OpenIdConnectParameterNames.IdToken);
|
||||||
message.IdTokenHint = principal?.FindFirst(OpenIdConnectParameterNames.IdToken)?.Value;
|
|
||||||
|
|
||||||
var redirectContext = new RedirectContext(Context, Options, properties)
|
var redirectContext = new RedirectContext(Context, Options, properties)
|
||||||
{
|
{
|
||||||
ProtocolMessage = message
|
ProtocolMessage = message
|
||||||
|
|
@ -513,9 +511,9 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
||||||
tokenEndpointResponse = authenticationValidatedContext.TokenEndpointResponse;
|
tokenEndpointResponse = authenticationValidatedContext.TokenEndpointResponse;
|
||||||
ticket = authenticationValidatedContext.Ticket;
|
ticket = authenticationValidatedContext.Ticket;
|
||||||
|
|
||||||
if (Options.SaveTokensAsClaims)
|
if (Options.SaveTokens)
|
||||||
{
|
{
|
||||||
SaveTokens(ticket.Principal, tokenEndpointResponse ?? authorizationResponse, jwt.Issuer);
|
SaveTokens(ticket.Properties, tokenEndpointResponse ?? authorizationResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Options.GetClaimsFromUserInfoEndpoint)
|
if (Options.GetClaimsFromUserInfoEndpoint)
|
||||||
|
|
@ -693,32 +691,28 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="principal">The principal in which tokens are saved.</param>
|
/// <param name="principal">The principal in which tokens are saved.</param>
|
||||||
/// <param name="message">The OpenID Connect response.</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))
|
if (!string.IsNullOrEmpty(message.AccessToken))
|
||||||
{
|
{
|
||||||
identity.AddClaim(new Claim(OpenIdConnectParameterNames.AccessToken, message.AccessToken,
|
tokens.Add(new AuthenticationToken { Name = OpenIdConnectParameterNames.AccessToken, Value = message.AccessToken });
|
||||||
ClaimValueTypes.String, issuer));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(message.IdToken))
|
if (!string.IsNullOrEmpty(message.IdToken))
|
||||||
{
|
{
|
||||||
identity.AddClaim(new Claim(OpenIdConnectParameterNames.IdToken, message.IdToken,
|
tokens.Add(new AuthenticationToken { Name = OpenIdConnectParameterNames.IdToken, Value = message.IdToken });
|
||||||
ClaimValueTypes.String, issuer));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(message.RefreshToken))
|
if (!string.IsNullOrEmpty(message.RefreshToken))
|
||||||
{
|
{
|
||||||
identity.AddClaim(new Claim(OpenIdConnectParameterNames.RefreshToken, message.RefreshToken,
|
tokens.Add(new AuthenticationToken { Name = OpenIdConnectParameterNames.RefreshToken, Value = message.RefreshToken });
|
||||||
ClaimValueTypes.String, issuer));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(message.TokenType))
|
if (!string.IsNullOrEmpty(message.TokenType))
|
||||||
{
|
{
|
||||||
identity.AddClaim(new Claim(OpenIdConnectParameterNames.TokenType, message.TokenType,
|
tokens.Add(new AuthenticationToken { Name = OpenIdConnectParameterNames.TokenType, Value = message.TokenType });
|
||||||
ClaimValueTypes.String, issuer));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(message.ExpiresIn))
|
if (!string.IsNullOrEmpty(message.ExpiresIn))
|
||||||
|
|
@ -729,8 +723,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
||||||
var expiresAt = Options.SystemClock.UtcNow + TimeSpan.FromSeconds(value);
|
var expiresAt = Options.SystemClock.UtcNow + TimeSpan.FromSeconds(value);
|
||||||
// https://www.w3.org/TR/xmlschema-2/#dateTime
|
// https://www.w3.org/TR/xmlschema-2/#dateTime
|
||||||
// https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx
|
// https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx
|
||||||
identity.AddClaim(new Claim("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture),
|
tokens.Add(new AuthenticationToken { Name = "expires_at", Value = expiresAt.ToString("o", CultureInfo.InvariantCulture) });
|
||||||
ClaimValueTypes.DateTime, issuer));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,10 +88,12 @@ namespace Microsoft.AspNetCore.Authentication.Twitter
|
||||||
},
|
},
|
||||||
Options.ClaimsIssuer);
|
Options.ClaimsIssuer);
|
||||||
|
|
||||||
if (Options.SaveTokensAsClaims)
|
if (Options.SaveTokens)
|
||||||
{
|
{
|
||||||
identity.AddClaim(new Claim("access_token", accessToken.Token, ClaimValueTypes.String, Options.ClaimsIssuer));
|
properties.StoreTokens(new [] {
|
||||||
identity.AddClaim(new Claim("access_token_secret", accessToken.TokenSecret, ClaimValueTypes.String, Options.ClaimsIssuer));
|
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));
|
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; }
|
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>
|
/// <summary>
|
||||||
/// Gets or sets the time limit for completing the authentication flow (15 minutes by default).
|
/// Gets or sets the time limit for completing the authentication flow (15 minutes by default).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TimeSpan RemoteAuthenticationTimeout { get; set; } = TimeSpan.FromMinutes(15);
|
public TimeSpan RemoteAuthenticationTimeout { get; set; } = TimeSpan.FromMinutes(15);
|
||||||
|
|
||||||
public IRemoteAuthenticationEvents Events = new RemoteAuthenticationEvents();
|
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",
|
ClientId = "Test Id",
|
||||||
ClientSecret = "Test Secret",
|
ClientSecret = "Test Secret",
|
||||||
|
SaveTokens = true,
|
||||||
StateDataFormat = stateFormat,
|
StateDataFormat = stateFormat,
|
||||||
ClaimsIssuer = claimsIssuer,
|
ClaimsIssuer = claimsIssuer,
|
||||||
BackchannelHttpHandler = new TestHttpMessageHandler
|
BackchannelHttpHandler = new TestHttpMessageHandler
|
||||||
|
|
@ -334,6 +335,7 @@ namespace Microsoft.AspNetCore.Authentication.Google
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var properties = new AuthenticationProperties();
|
var properties = new AuthenticationProperties();
|
||||||
var correlationKey = ".xsrf";
|
var correlationKey = ".xsrf";
|
||||||
var correlationValue = "TestCorrelationId";
|
var correlationValue = "TestCorrelationId";
|
||||||
|
|
@ -361,6 +363,12 @@ namespace Microsoft.AspNetCore.Authentication.Google
|
||||||
|
|
||||||
// Ensure claims transformation
|
// Ensure claims transformation
|
||||||
Assert.Equal("yup", transaction.FindClaimValue("xform"));
|
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
|
// 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");
|
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"))
|
else if (req.Path == new PathString("/me"))
|
||||||
{
|
{
|
||||||
res.Describe(context.User);
|
res.Describe(context.User);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
|
@ -63,5 +64,23 @@ namespace Microsoft.AspNetCore.Authentication
|
||||||
var xmlBytes = Encoding.UTF8.GetBytes(xml.ToString());
|
var xmlBytes = Encoding.UTF8.GetBytes(xml.ToString());
|
||||||
res.Body.Write(xmlBytes, 0, xmlBytes.Length);
|
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;
|
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