Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace JObject with JsonDocument in Authentication #7105

Merged
merged 9 commits into from
Feb 5, 2019
Merged

Conversation

Tratcher
Copy link
Member

#4260

  • "Drop in" replacement with some odd gymnastics sometimes.
  • It's not as breaking as I thought it might be for derived OAuth handlers. ClaimActions shield them from most of the changes. They primarily have to call Parse with the new type. @PinpointTownes
  • Users doing advanced claims mapping or inspection via events may be broken.
  • Also fixed launch settings so the samples would work again.

@Tratcher Tratcher added the breaking-change This issue / pr will introduce a breaking change, when resolved / merged. label Jan 29, 2019
@Tratcher Tratcher added this to the 3.0.0-preview3 milestone Jan 29, 2019
@Tratcher Tratcher self-assigned this Jan 29, 2019
@Tratcher
Copy link
Member Author

@ahsonkhan

@Eilon Eilon added the area-auth Includes: Authn, Authz, OAuth, OIDC, Bearer label Jan 29, 2019
@Tratcher
Copy link
Member Author

If we get the C# 8 infrastructure working soon then we can use using var to clean up all of these new using blocks.

Copy link
Contributor

@kevinchalet kevinchalet left a comment

Choose a reason for hiding this comment

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

@Tratcher thanks for tagging, it's much appreciated 👍

The new JSON stack looks promising and it's good to know we now have a JObject-like API, which is much more convenient than directly using a low-level JSON reader.

When you have a moment, you may want to take a look at the aspnet-contrib providers to ensure we have everything we need. Some of our providers use some nasty hacks to work around non-standard implementations, like re-creating JObject from query string parameters (e.g StackExchange) or extracting the user profile from the token response (e.g Myob).


return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name);

using (var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync()))
Copy link
Contributor

Choose a reason for hiding this comment

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

Out of curiosity, do you happen to know why JsonDocument implements IDisposable?

Copy link
Contributor

Choose a reason for hiding this comment

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

(at some point, we may want to move away from ReadAsStringAsync() in both the "official" and aspnet-contrib providers. I'm even surprised @davidfowl didn't cringe about the fact it's allocatey as hell 😄)

Copy link
Member Author

Choose a reason for hiding this comment

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

It's disposable because it uses pooled buffers.

Sticking with ReadAsStringAsync might be a good idea. I know it's a bit less efficient but it does handle a bunch of decoding issues for us. The new json reader is hardcoded to UTF8.

Copy link
Member

Choose a reason for hiding this comment

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

Why would we keep using ReadAsStringAsync ?

Copy link
Member

Choose a reason for hiding this comment

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

https://github.com/dotnet/corefx/blob/28e216680a990ce4a58798aa0d2026c9d3e1f148/src/System.Net.Http/src/System/Net/Http/HttpContent.cs#L189-L245

How about this, if the encoding is UTF8, use the fast path (don't go byte[] -> string -> JsonDocument), if it's not UTF8, in those 2% cases, then use ReadAsStringAsync.

Copy link
Member Author

@Tratcher Tratcher Jan 30, 2019

Choose a reason for hiding this comment

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

ReadAsStringAsync deals with charsets and BOMs for us. ReadBufferAsString is exactly the method we don't want to re-implement, it would be useful if it were public.

This code path is not sufficiently perf sensitive to warrant a fast and slow path. Logins are a 0.1% scenario. Correctness is far more important and easier to maintain with a single code path.

Copy link
Member

Choose a reason for hiding this comment

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

@Tratcher We need performance tests for auth, we have none today and we know it performs poorly. I buy that it isn't on the hot path but lets not get lazy with our performance work. It's a tiny set of code to make it do a better thing in the 90% case. We can always fallback in the other scenarios.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes we need perf tests for auth that runs on most requests like Cookie & Bearer. OAuth and OIDC are much lower priority when the results are cached for weeks.

Copy link
Member

Choose a reason for hiding this comment

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

OK lets agree to get those added as part of preview3. I'd like to take this opportunity to know where we stand.

Copy link
Member Author

Choose a reason for hiding this comment

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

Filed #7162

Copy link
Member

@ahsonkhan ahsonkhan left a comment

Choose a reason for hiding this comment

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

Given JsonDocument is disposable, we should consider who ends up disposing it, and consider passing JsonElement around that points to the sub-payload rather than the whole JsonDocument. Granted there is a usability burden here, but its a side effect of having a high-performance implementation which emphasizes on low allocations and performance.

@@ -211,8 +211,8 @@ await context.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme, new Authe
using (var payload = JsonDocument.Parse(await tokenResponse.Content.ReadAsStringAsync()))
{
// Persist the new acess token
props.UpdateTokenValue("access_token", payload.GetString("access_token"));
props.UpdateTokenValue("refresh_token", payload.GetString("refresh_token"));
props.UpdateTokenValue("access_token", payload.RootElement.GetString("access_token"));
Copy link
Member

Choose a reason for hiding this comment

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

nit: cache RootElement in a local wherever its references 3+ times.

Copy link
Member

@ahsonkhan ahsonkhan left a comment

Choose a reason for hiding this comment

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

Other than ToString() usage and some redundant enumeration, LGTM!

@Tratcher
Copy link
Member Author

Tratcher commented Feb 1, 2019

/AzurePipelines run AspNetCore-ci

@azure-pipelines
Copy link

Successfully queued 1 pipeline(s).

@Tratcher
Copy link
Member Author

Tratcher commented Feb 1, 2019

@HaoK rebased and ready for a final review.

@Tratcher
Copy link
Member Author

Tratcher commented Feb 4, 2019

/AzurePipelines run AspNetCore-ci

@azure-pipelines
Copy link

Successfully queued 1 pipeline(s).

}
else if (contentType.MediaType.Equals("application/jwt", StringComparison.OrdinalIgnoreCase))
{
var userInfoEndpointJwt = new JwtSecurityToken(userInfoResponse);
user = JObject.FromObject(userInfoEndpointJwt.Payload);
user = JsonDocument.Parse(userInfoEndpointJwt.Payload.SerializeToJson());
Copy link
Member

Choose a reason for hiding this comment

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

This is kinda gross. This isn't a hot path either right?

cc @ahsonkhan Some potential API feedback here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Not a hot path. It's a corner case that I haven't seen anyone use. We can talk to IdentityModel about giving us the raw json. @brentschmaltz

Copy link
Member

@davidfowl davidfowl Feb 5, 2019

Choose a reason for hiding this comment

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

Still, its is just a crappything to have to do.

user = userInformationReceivedContext.User;

Options.ProtocolValidator.ValidateUserInfoResponse(new OpenIdConnectProtocolValidationContext()
using (user)
Copy link
Member

Choose a reason for hiding this comment

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

This new using is unfortunate but I guess we can't avoid it :/

Copy link
Member Author

Choose a reason for hiding this comment

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

Most of them can get squashed with the new C# 8 using syntax.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-auth Includes: Authn, Authz, OAuth, OIDC, Bearer breaking-change This issue / pr will introduce a breaking change, when resolved / merged.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants