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

Conversation

@Tratcher
Copy link
Member

commented Jan 29, 2019

#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 this to the 3.0.0-preview3 milestone Jan 29, 2019
@Tratcher Tratcher self-assigned this Jan 29, 2019
@Tratcher Tratcher requested review from HaoK and BrennanConroy Jan 29, 2019
@Tratcher

This comment has been minimized.

Copy link
Member Author

commented Jan 29, 2019

@ahsonkhan

@Eilon Eilon added the area-security label Jan 29, 2019
@Tratcher

This comment has been minimized.

Copy link
Member Author

commented Jan 29, 2019

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

left a comment

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

This comment has been minimized.

Copy link
@PinpointTownes

PinpointTownes Jan 29, 2019

Contributor

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

This comment has been minimized.

Copy link
@PinpointTownes

PinpointTownes Jan 29, 2019

Contributor

(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 😄)

This comment has been minimized.

Copy link
@Tratcher

Tratcher Jan 30, 2019

Author Member

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.

This comment has been minimized.

Copy link
@davidfowl

davidfowl Jan 30, 2019

Member

Why would we keep using ReadAsStringAsync ?

This comment has been minimized.

Copy link
@davidfowl

davidfowl Jan 30, 2019

Member

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.

This comment has been minimized.

Copy link
@Tratcher

Tratcher Jan 30, 2019

Author Member

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.

This comment has been minimized.

Copy link
@davidfowl

davidfowl Jan 30, 2019

Member

@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.

This comment has been minimized.

Copy link
@Tratcher

Tratcher Jan 30, 2019

Author Member

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.

This comment has been minimized.

Copy link
@davidfowl

davidfowl Jan 31, 2019

Member

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

This comment has been minimized.

Copy link
@Tratcher

Tratcher Jan 31, 2019

Author Member

Filed #7162

Copy link
Contributor

left a comment

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

This comment has been minimized.

Copy link
@ahsonkhan

ahsonkhan Jan 30, 2019

Contributor

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

Copy link
Contributor

left a comment

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

@Tratcher

This comment has been minimized.

Copy link
Member Author

commented Feb 1, 2019

/AzurePipelines run AspNetCore-ci

@azure-pipelines

This comment has been minimized.

Copy link

commented Feb 1, 2019

Successfully queued 1 pipeline(s).

@Tratcher Tratcher force-pushed the tratcher/nojson branch from 7b62f66 to 1d34d50 Feb 1, 2019
@Tratcher

This comment has been minimized.

Copy link
Member Author

commented Feb 1, 2019

@HaoK rebased and ready for a final review.

@Tratcher

This comment has been minimized.

Copy link
Member Author

commented Feb 4, 2019

/AzurePipelines run AspNetCore-ci

@azure-pipelines

This comment has been minimized.

Copy link

commented Feb 4, 2019

Successfully queued 1 pipeline(s).

@Tratcher Tratcher force-pushed the tratcher/nojson branch from 1d34d50 to bee3e86 Feb 4, 2019
}
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());

This comment has been minimized.

Copy link
@davidfowl

davidfowl Feb 5, 2019

Member

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

cc @ahsonkhan Some potential API feedback here.

This comment has been minimized.

Copy link
@Tratcher

Tratcher Feb 5, 2019

Author Member

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

This comment has been minimized.

Copy link
@davidfowl

davidfowl Feb 5, 2019

Member

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

user = userInformationReceivedContext.User;

Options.ProtocolValidator.ValidateUserInfoResponse(new OpenIdConnectProtocolValidationContext()
using (user)

This comment has been minimized.

Copy link
@HaoK

HaoK Feb 5, 2019

Member

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

This comment has been minimized.

Copy link
@Tratcher

Tratcher Feb 5, 2019

Author Member

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

@HaoK
HaoK approved these changes Feb 5, 2019
@Tratcher Tratcher merged commit 67037a0 into master Feb 5, 2019
2 checks passed
2 checks passed
AspNetCore-ci Build #20190204.32 succeeded
Details
license/cla All CLA requirements met.
Details
@Tratcher Tratcher deleted the tratcher/nojson branch Feb 5, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.