Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.

Save tokens in auth properties instead of claims #698

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 38 additions & 30 deletions samples/SocialSample/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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/
Expand All @@ -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
Expand Down Expand Up @@ -318,6 +319,13 @@ dnx web
{
await context.Response.WriteAsync(claim.Type + ": " + claim.Value + "<br>");
}

await context.Response.WriteAsync("Tokens:<br>");

await context.Response.WriteAsync("Access Token: " + await context.Authentication.GetTokenAsync("access_token") + "<br>");
await context.Response.WriteAsync("Refresh Token: " + await context.Authentication.GetTokenAsync("refresh_token") + "<br>");
await context.Response.WriteAsync("Token Type: " + await context.Authentication.GetTokenAsync("token_type") + "<br>");
await context.Response.WriteAsync("expires_at: " + await context.Authentication.GetTokenAsync("expires_at") + "<br>");
await context.Response.WriteAsync("<a href=\"/logout\">Logout</a>");
await context.Response.WriteAsync("</body></html>");
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,14 @@ protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
return AuthenticateResult.Skip();
}

if (Options.SaveToken)
{
ticket.Properties.StoreTokens(new[]
{
new AuthenticationToken { Name = "access_token", Value = token }
});
}

return AuthenticateResult.Success(ticket);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,11 @@ public JwtBearerOptions() : base()
/// <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;
}
}
23 changes: 13 additions & 10 deletions src/Microsoft.AspNetCore.Authentication.OAuth/OAuthHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,35 +85,38 @@ protected override async Task<AuthenticateResult> 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<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))
{
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",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, no plan to make it a property on AuthenticationToken?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't sound like you've convinced @Tratcher and it definitely makes things more complicated, so my default is no for now as well, you can file an issue to consider adding it later

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, will do.

Value = expiresAt.ToString("o", CultureInfo.InvariantCulture)
});
}
}

properties.StoreTokens(authTokens);
}

return AuthenticateResult.Success(await CreateTicketAsync(identity, properties, tokens));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, it's not required.

var redirectContext = new RedirectContext(Context, Options, properties)
{
ProtocolMessage = message
Expand Down Expand Up @@ -513,9 +511,9 @@ protected override async Task<AuthenticateResult> 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)
Expand Down Expand Up @@ -693,32 +691,28 @@ protected virtual async Task<AuthenticateResult> GetUserInformationAsync(OpenIdC
/// </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))
Expand All @@ -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) });
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,12 @@ protected override async Task<AuthenticateResult> 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));
Expand Down
11 changes: 11 additions & 0 deletions src/Microsoft.AspNetCore.Authentication/AuthenticationToken.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,19 @@ public string DisplayName
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; }
}
}
Loading