JudahGabriel / RavenDB.Identity

RavenDB Identity provider for ASP.NET Core. Let RavenDB manage your users and logins.
https://www.nuget.org/packages/RavenDB.Identity/1.0.0
MIT License
61 stars 29 forks source link

Serialization error using dotnet core preview 8 #16

Closed marcuslindblom closed 5 years ago

marcuslindblom commented 5 years ago

I'm using version 6.1 of RavenDB.Identity with dotnet core preview 8.

My config looks like this:

services.Configure<RavenSettings>(Configuration.GetSection("RavenSettings"));
services.AddRavenDbDocStore()
    .AddRavenDbAsyncSession()
    .AddRavenDbIdentity<AppUser>()
    .AddDefaultUI();

services.AddAuthentication()
    AddGoogle(o =>
    {
        o.ClientId = "xxx.apps.googleusercontent.com";
        o.ClientSecret = "xxx";
    });

services.AddControllersWithViews().AddMvcOptions(o => o.Filters.Add<RavenSaveChangesAsyncFilter>());
services.AddRazorPages();

When I try to register a new account using Google I get a System.PlatformNotSupportedException: This instance contains state that cannot be serialized and deserialized on this platform. with the following stack trace.

fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.PlatformNotSupportedException: This instance contains state that cannot be serialized and deserialized on this platform.
   at System.Security.Claims.ClaimsPrincipal.OnSerializingMethod(StreamingContext context)
   --- End of inner exception stack trace ---
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
   at Newtonsoft.Json.Serialization.JsonContract.<>c__DisplayClass57_0.<CreateSerializationCallback>b__0(Object o, StreamingContext context)
   at Newtonsoft.Json.Serialization.JsonContract.InvokeOnSerializing(Object o, StreamingContext context)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.OnSerializing(JsonWriter writer, JsonContract contract, Object value)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeList(JsonWriter writer, IEnumerable values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType)
   at Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter, Object value, Type objectType)
   at Newtonsoft.Json.JsonSerializer.Serialize(JsonWriter jsonWriter, Object value)
   at Raven.Client.Documents.Session.EntityToBlittable.ConvertEntityToBlittable(Object entity, DocumentInfo documentInfo) in C:\Builds\RavenDB-Stable-4.2\42017\src\Raven.Client\Documents\Session\EntityToBlittable.cs:line 43
   at Raven.Client.Documents.Session.InMemoryDocumentSessionOperations.PrepareForEntitiesPuts(SaveChangesData result) in C:\Builds\RavenDB-Stable-4.2\42017\src\Raven.Client\Documents\Session\InMemoryDocumentSessionOperations.cs:line 971
   at Raven.Client.Documents.Session.InMemoryDocumentSessionOperations.PrepareForSaveChanges() in C:\Builds\RavenDB-Stable-4.2\42017\src\Raven.Client\Documents\Session\InMemoryDocumentSessionOperations.cs:line 800
   at Raven.Client.Documents.Session.Operations.BatchOperation.CreateRequest() in C:\Builds\RavenDB-Stable-4.2\42017\src\Raven.Client\Documents\Session\Operations\BatchOperation.cs:line 32
   at Raven.Client.Documents.Session.AsyncDocumentSession.SaveChangesAsync(CancellationToken token) in C:\Builds\RavenDB-Stable-4.2\42017\src\Raven.Client\Documents\Session\AsyncDocumentSession.cs:line 135
   at WebApp3.RavenSaveChangesAsyncFilter.OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next) in /Users/marcus/Temp/WebApp3/Startup.cs:line 181
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeNextPageFilterAsync()
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Rethrow(PageHandlerExecutedContext context)
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeInnerFilterAsync()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

Maybe this has to do with the new System.Text.Json APIs? Or do you have any clue on whats has changed?

JudahGabriel commented 5 years ago

Hey Marcus,

I'm out of the country at the moment, but I'll return in a week and have a look then.

marcuslindblom commented 5 years ago

The problem seems to be when I use ExternalLoginInfo. This code throws an exception.

var info = await _signInManager.GetExternalLoginInfoAsync();
var email = info.Principal.FindFirstValue(ClaimTypes.Email);
var user = await _userManager.FindByEmailAsync(email);
await _userManager.AddLoginAsync(user, info);
await _session.SaveChangesAsync();

If I create a new UserLoginInfo it seems to work like expected.

var info = await _signInManager.GetExternalLoginInfoAsync();
var email = info.Principal.FindFirstValue(ClaimTypes.Email);
var user = await _userManager.FindByEmailAsync(email);
await _userManager.AddLoginAsync(user, new UserLoginInfo(info.LoginProvider, info.ProviderKey, info.ProviderDisplayName));
await _session.SaveChangesAsync();
JudahGabriel commented 5 years ago

I'm back from my travels and I'm looking at this today. I'll let you know what I find.

JudahGabriel commented 5 years ago

Hi Marcus,

So it looks like folks on other databases hit a similar problem, where Newtonsoft.Json can't serialize claims.

The workaround shown in that thread is to use a customer serializer for claims, principals, and identities.

For Raven, I think the way that would work is like this:

docStore.Conventions.CustomizeJsonSerializer = (serializer) =>
{
    serializer.Converters.Add(new JsonClaimConverter());
    serializer.Converters.Add(new JsonClaimsPrincipalConverter());
    serializer.Converters.Add(new JsonClaimsIdentityConverter());
};

And here are the implementations for those:

Can you give that a try and see if it works?

marcuslindblom commented 5 years ago

@JudahGabriel That seems to work. I guess this serialized data seems like the correct one?

"Logins": [
{
            "$type": "Microsoft.AspNetCore.Identity.ExternalLoginInfo, Microsoft.AspNetCore.Identity",
            "Principal": {
                "Identities": [
                    {
                        "AuthenticationType": "Google",
                        "IsAuthenticated": true,
                        "Actor": null,
                        "BootstrapContext": null,
                        "Claims": [
                            {
                                "Type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
                                "Value": "112489450863147312520",
                                "ValueType": "http://www.w3.org/2001/XMLSchema#string",
                                "Issuer": "Google",
                                "OriginalIssuer": "Google"
                            },
                            {
                                "Type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
                                "Value": "Marcus Lindblom",
                                "ValueType": "http://www.w3.org/2001/XMLSchema#string",
                                "Issuer": "Google",
                                "OriginalIssuer": "Google"
                            },
                            {
                                "Type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
                                "Value": "Marcus",
                                "ValueType": "http://www.w3.org/2001/XMLSchema#string",
                                "Issuer": "Google",
                                "OriginalIssuer": "Google"
                            },
                            {
                                "Type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname",
                                "Value": "Lindblom",
                                "ValueType": "http://www.w3.org/2001/XMLSchema#string",
                                "Issuer": "Google",
                                "OriginalIssuer": "Google"
                            },
                            {
                                "Type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
                                "Value": "m@xxx.com",
                                "ValueType": "http://www.w3.org/2001/XMLSchema#string",
                                "Issuer": "Google",
                                "OriginalIssuer": "Google"
                            }
                        ],
                        "Label": null,
                        "Name": "Marcus Lindblom",
                        "NameClaimType": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
                        "RoleClaimType": "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
                    }
                ]
            },
            "AuthenticationTokens": [],
            "LoginProvider": "Google",
            "ProviderKey": "xxx",
            "ProviderDisplayName": "Google"
        }
]
JudahGabriel commented 5 years ago

Looks good to me. Since this is a viable workaround, I'm going to close this issue.

gabrielbarceloscn commented 4 years ago

Hi @marcuslindblom . How are you?

Have you tried to update this work around to RavenDB 5 client?

image

Looks like they changed the conventions override path. =/

marcuslindblom commented 4 years ago

@gabrielbarceloscn I have successfully upgraded my projects. I'll post some code when I get back home

marcuslindblom commented 4 years ago

@gabrielbarceloscn Change to code to the following and it all should work again 👍

options.BeforeInitializeDocStore = store =>
{
  store.Conventions.Serialization = new NewtonsoftJsonSerializationConventions {
    CustomizeJsonSerializer = (serializer) => {
      serializer.Converters.Add(new JsonClaimConverter());
      serializer.Converters.Add(new JsonClaimsPrincipalConverter());
      serializer.Converters.Add(new JsonClaimsIdentityConverter());
    }
  };
ta1H3n commented 2 months ago

I had the same issue and registering Claim converters as suggested above fixed the issue.

But I later found that I forgot to await the main thread that kicked off the serialisation. After adding back the await everything works as expected even without the custom claim converters