I am trying to use Identityserver4's new Mutual TLS client authentication. I have followed the documents in the Identityserver4 web site (Mutual TLS).
When I tried to a get a access token for mtls client, I get error "Forbidden". When I inspected the IdentityServer4 log file:
2019-06-11 10:19:26.690 +00:00 [INF] Request finished in 23.3151ms 200 application/json; charset=UTF-8
2019-06-11 10:19:26.784 +00:00 [INF] Request starting HTTP/1.1 GET http://open-banking-authorisation-server-host/.well-known/openid-configuration/jwks
2019-06-11 10:19:26.786 +00:00 [INF] Invoking IdentityServer endpoint: IdentityServer4.Endpoints.DiscoveryKeyEndpoint for /.well-known/openid-configuration/jwks
2019-06-11 10:19:26.816 +00:00 [INF] Request finished in 32.05ms 200 application/jwk-set+json; charset=UTF-8
2019-06-11 10:20:41.797 +00:00 [INF] Request starting HTTP/1.1 POST http://open-banking-authorisation-server-host/connect/mtls/token application/x-www-form-urlencoded 80
2019-06-11 10:20:41.814 +00:00 [INF] AuthenticationScheme: x509 was forbidden.
Could anyone help please?
var clientId = "adsjasdjakafklfalvf";
FileStream f = new FileStream("client_cert.crt", FileMode.Open, FileAccess.Read);
int size = (int)f.Length;
byte[] data = new byte[size];
size = f.Read(data, 0, size);
f.Close();
var cert = new X509Certificate2(data);
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(cert);
var newClient = new HttpClient(handler);
var tokenResponse = await newClient.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = $"{_authorityBaseUri}/connect/mtls/token",
ClientId = clientId,
Scope = "accounts"
});
var accessToken = tokenResponse.AccessToken;
newClient.Dispose();
you should
Enable SSL in IS4.
Create client certificate.
Enable IIS Express to accept client certificate.
Client App
static async Task<TokenResponse> RequestTokenAsync2()
{
var handler = new SocketsHttpHandler();
var cert = new X509Certificate2("mtls.test-client.p12", "changeit");
handler.SslOptions.ClientCertificates = new X509CertificateCollection { cert };
var client = new HttpClient(handler);
var disco = await client.GetDiscoveryDocumentAsync("https://localhost:44302");
if (disco.IsError) throw new Exception(disco.Error);
var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco
.TryGetValue(OidcConstants.Discovery.MtlsEndpointAliases)
.Value<string>(OidcConstants.Discovery.TokenEndpoint)
.ToString(),
ClientId = "mtls",
Scope = "api1"
});
if (response.IsError) throw new Exception(response.Error);
return response;
}
IS4
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication()
.AddCertificate(options =>
{
options.AllowedCertificateTypes = CertificateTypes.All;
options.RevocationMode = X509RevocationMode.NoCheck;
})
var builder = services.AddIdentityServer(options =>
{
options.MutualTls.Enabled = true;
options.MutualTls.ClientCertificateAuthenticationScheme = "Certificate";
options.Events.RaiseErrorEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseSuccessEvents = true;
})
.AddInMemoryIdentityResources(Config.Ids)
.AddInMemoryApiResources(Config.Apis)
.AddInMemoryClients(Config.Clients)
.AddTestUsers(TestUsers.Users);
builder.AddMutualTlsSecretValidators();
builder.AddDeveloperSigningCredential();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//app.useh
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
Further reading:
http://docs.identityserver.io/en/latest/topics/mtls.html#
https://leastprivilege.com/2020/02/07/mutual-tls-and-proof-of-possession-access-tokens-part-1-setup/
https://improveandrepeat.com/2017/07/how-to-configure-iis-express-to-accept-ssl-client-certificates/
https://www.scottbrady91.com/ASPNET/Using-mkcert-for-ASPNET-Core-Development
Related
I am using IdentityServer4 (IS4) to connect to AzureAD for authentication. I have created the app on AzureAD and used the correct ClientID and Tenant ID as well.
I am getting the following error while signing in:
[15:13:04 Information] Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler
AuthenticationScheme: OpenIdConnect was challenged.
[15:13:06 Debug] IdentityServer4.Hosting.CorsPolicyProvider
CORS request made for path: /signin-oidc from origin: https://login.microsoftonline.com but was ignored because path was not for an allowed IdentityServer CORS endpoint
[15:13:06 Information] Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler
AuthenticationScheme: Identity.External signed in.
Please, I requesting to guide me, as in what is going wrong here.
Here's how my entire Startup.cs looks like:
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
using IdentityServer4;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using IdentityServerHost.Quickstart.UI;
using System.Reflection;
using IdentityServer.Models;
using Microsoft.AspNetCore.Identity;
using IdentityServer.Data;
using IdentityServer4.Configuration;
using System;
using Microsoft.AspNetCore.Authentication;
using IdentityServer4.Services;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
namespace IdentityServer
{
public class Startup
{
public IWebHostEnvironment Environment { get; }
public IConfiguration Configuration { get; }
public Startup(IWebHostEnvironment environment, IConfiguration configuration)
{
Environment = environment;
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
var connectionString = Configuration.GetConnectionString("DefaultConnection");
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
services.AddControllersWithViews();
services.AddDbContext<IdentityServerContext>(options =>
options.UseMySql(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly))
);
services.AddDbContext<Data.ConfigurationDbContext>(options => options.UseMySql(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)));
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.SignIn.RequireConfirmedEmail = false;
//New added
options.Password.RequiredLength = 4;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
options.Password.RequireDigit = false;
options.Password.RequireNonAlphanumeric = false;
options.Lockout.AllowedForNewUsers = true;
options.Lockout.DefaultLockoutTimeSpan = new TimeSpan(0, 15, 00);
options.Lockout.MaxFailedAccessAttempts = 5;
})
.AddEntityFrameworkStores<IdentityServerContext>()
.AddDefaultTokenProviders();
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
// see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
options.EmitStaticAudienceClaim = true;
options.UserInteraction.LoginUrl = "/Account/Login";
options.UserInteraction.LogoutUrl = "/Account/Logout";
options.Authentication = new IdentityServer4.Configuration.AuthenticationOptions()
{
CookieLifetime = TimeSpan.FromHours(10), // ID server cookie timeout set to 10 hours
CookieSlidingExpiration = true
};
})
//.AddTestUsers(TestUsers.Users)
// this adds the config data from DB (clients, resources, CORS)
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = builder => builder.UseMySql(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
})
// this adds the operational data from DB (codes, tokens, consents)
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder => builder.UseMySql(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
// this enables automatic token cleanup. this is optional.
options.EnableTokenCleanup = true;
})
.AddAspNetIdentity<ApplicationUser>()
.AddProfileService<IdentityProfileService>();
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
var autBuilder = services.AddAuthentication();
//Azure AD
autBuilder.AddAzureAd(options => Configuration.Bind("AzureAd", options));
// not recommended for production - you need to store your key material somewhere secure
builder.AddDeveloperSigningCredential();
/*
services.AddAuthentication()
.AddGoogle(options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
// register your IdentityServer with Google at https://console.developers.google.com
// enable the Google+ API
// set the redirect URI to https://localhost:5001/signin-google
options.ClientId = "copy client ID from Google here";
options.ClientSecret = "copy client secret from Google here";
});
*/
}
public void Configure(IApplicationBuilder app)
{
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseCors("CorsPolicy");
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
}
}
And the Azure Extension.cs
using System;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Authentication
{
public static class AzureAdAuthenticationBuilderExtensions
{
public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder)
=> builder.AddAzureAd(_ => { });
public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder, Action<AzureAdOptions> configureOptions)
{
builder.Services.Configure(configureOptions);
builder.Services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, ConfigureAzureOptions>();
builder.AddOpenIdConnect();
return builder;
}
private class ConfigureAzureOptions: IConfigureNamedOptions<OpenIdConnectOptions>
{
private readonly AzureAdOptions _azureOptions;
public ConfigureAzureOptions(IOptions<AzureAdOptions> azureOptions)
{
_azureOptions = azureOptions.Value;
}
public void Configure(string name, OpenIdConnectOptions options)
{
options.ClientId = _azureOptions.ClientId;
options.Authority = $"{_azureOptions.Instance}{_azureOptions.TenantId}";
options.UseTokenLifetime = true;
options.CallbackPath = _azureOptions.CallbackPath;
options.RequireHttpsMetadata = false;
}
public void Configure(OpenIdConnectOptions options)
{
Configure(Options.DefaultName, options);
}
}
}
}
And the appsettings.json
Appsettings.json
{
"ConnectionStrings": {
//"DefaultConnection": "connectiong_string"
},
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "",
"TenantId": "xxxxxxxxxxxxxxxxxxxxxxxxxx",
"ClientId": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"CallbackPath": "/signin-oidc"
},
"TimeSettings": {
"AbsoluteRefreshTokenLifetime": 15552000,
"SlidingRefreshTokenLifetime": 1296000,
"IdentityTokenLifetime": 300,
"AccessTokenLifetime": 300,
"AuthorizationCodeLifetime": 300
}
}
IdentityServer has its own CORS settings for requests made by its clients.
You use the AllowedCorsOrigins collection on the client configuration to set this up. Simply add the origin of the client to the collection and the default configuration in IdentityServer will consult these values to allow cross-origin calls from the origins.
new Client
{
...
AllowedCorsOrigins = new List<string>
{
"http://www.myclient.com"
}
}
Hard to tell, one thought is if the use of "CallbackPath": "/signin-oidc" should be changed to some other URL to not conflict with other things that uses /signin-oidc.
For example in the code here they use:
.AddOpenIdConnect("aad", "Sign-in with Azure AD", options =>
{
...
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.ResponseType = "id_token";
options.CallbackPath = "/signin-aad";
options.SignedOutCallbackPath = "/signout-callback-aad";
options.RemoteSignOutPath = "/signout-aad";
I have a Blazor web app that connects to a different Identity Server 4 server. I can get the login to work correctly and pass the access token back the Blazor. However, when the token expires I don't know how to go out and get a new access token? Should I be getting a refresh token and then an access token? I am confused on how this all works.
Blazor Code
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = AzureADDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(AzureADDefaults.AuthenticationScheme, options =>
{
options.Authority = "https://localhost:44382";
options.RequireHttpsMetadata = true;
options.ClientId = "client";
options.ClientSecret = "secret";
options.ResponseType = "code id_token token";
options.SaveTokens = true;
options.Scope.Add("IdentityServerApi");
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("roles");
options.Scope.Add("offline_access");
});
IdentityServer4 Setup
...
new Client
{
ClientId = "client",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.Hybrid,
AllowAccessTokensViaBrowser = true,
RequireClientSecret = true,
RequireConsent = false,
RedirectUris = { "https://localhost:44370/signin-oidc" },
PostLogoutRedirectUris = { "https://localhost:44370/signout-callback-oidc" },
AllowedScopes = { "openid", "profile", "email", "roles", "offline_access",
IdentityServerConstants.LocalApi.ScopeName
},
AllowedCorsOrigins = { "https://localhost:44370" },
AlwaysSendClientClaims = true,
AlwaysIncludeUserClaimsInIdToken = true,
AllowOfflineAccess = true,
AccessTokenLifetime = 1,//testing
UpdateAccessTokenClaimsOnRefresh = true
},
...
UPDATE:
I have updated my code to offline_access for the client and server (thanks for the update below). My next question is how do I inject the request for the refresh token in Blazor once I get rejected because the access token is expired?
I have the Blazor app making calls back to the API (which validates the access token).
public class APIClient : IAPIClient
{
private readonly HttpClient _httpClient;
//add the bearer token to the APIClient when the client is used
public APIClient(IHttpContextAccessor httpAccessor, HttpClient client, IConfiguration configuration)
{
var accessToken = httpAccessor.HttpContext.GetTokenAsync("access_token").Result;
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
client.DefaultRequestVersion = new Version(2, 0);
client.BaseAddress = new Uri(configuration["Api_Location"]);
_httpClient = client;
_logger = logger;
}
What do I need to add to my API calls to validate?
Yes, you should obtain a refresh token as well to keep getting new access tokens. To get a refresh token from IdentityServer you need to add the 'offline_access' scope in the 'AllowedScopes' property of your client. You also need to set the 'AllowOfflineAccess' property on your client to true.
After that you need to include 'offline_access' to the scopes sent by the client and you should receive a refresh token in the response.
To use the refresh token, send a request to the token endpoint with everything you sent for the code exchange except replace the 'code' param with 'refresh_token' and change the value for 'grant_type' from 'code' to 'refresh_token'. The IdentityServer4 response to this request should contain an id_token, an access_token, and a new refresh_token.
I think I have found an answer (given the push from Randy). I did something familiar to this post, where I created a generic method in my APIClient.
public async Task<T> SendAsync<T>(HttpRequestMessage requestMessage)
{
var response = await _httpClient.SendAsync(requestMessage);
//test for 403 and actual bearer token in initial request
if (response.StatusCode == HttpStatusCode.Unauthorized &&
requestMessage.Headers.Where(c => c.Key == "Authorization")
.Select(c => c.Value)
.Any(c => c.Any(p => p.StartsWith("Bearer"))))
{
var pairs = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "refresh_token"),
new KeyValuePair<string, string>("refresh_token", _httpAccessor.HttpContext.GetTokenAsync("refresh_token").Result),
new KeyValuePair<string, string>("client_id", "someclient"),
new KeyValuePair<string, string>("client_secret", "*****")
};
//retry do to token request
using (var refreshResponse = await _httpClient.SendAsync(
new HttpRequestMessage(HttpMethod.Post, new Uri(_authLocation + "connect/token"))
{
Content = new FormUrlEncodedContent(pairs)})
)
{
var rawResponse = await refreshResponse.Content.ReadAsStringAsync();
var x = Newtonsoft.Json.JsonConvert.DeserializeObject<Data.Models.Token>(rawResponse);
var info = await _httpAccessor.HttpContext.AuthenticateAsync("Cookies");
info.Properties.UpdateTokenValue("refresh_token", x.Refresh_Token);
info.Properties.UpdateTokenValue("access_token", x.Access_Token);
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", x.Access_Token);
//retry actual request with new tokens
response = await _httpClient.SendAsync(new HttpRequestMessage(requestMessage.Method, requestMessage.RequestUri));
}
}
if (typeof(T).Equals(typeof(HttpResponseMessage)))
return (T)Convert.ChangeType(response, typeof(T));
else
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(await response.Content.ReadAsStringAsync());
}
I don't like that I have to call AuthenticateAsync. Yet, that seems to be the way I have found to get access to the UpdateTokenValue method to delete and then re-add the new access token.
I host my identity server use address http://10.2.5.90:8000 and use nginx map https://10.2.5.90:8888 to http://10.2.5.90:8000.
When i tried to request discovery document like quickstart in client, "Error connecting to http://localhost:8000/.well-known/openid-configuration/jwks. Object reference not set to an instance of an object.." occurred.
I tried to change Issuer to https address and used customized DiscoveryDocumentRequest. But it was not work.
When I remove the nginx and access http://10.2.5.90:8000, It worked well.
IdentityServer:Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// ...other codes
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
options.IssuerUri = "https://10.2.5.90:8888";
});
// ...other codes
}
Client: Program.cs
private static async Task<string> GetAccessTokenAsync()
{
var client = new HttpClient();
var req = new DiscoveryDocumentRequest
{
Address = "https://10.2.5.90:8888",
Policy = new DiscoveryPolicy
{
ValidateIssuerName = false,
ValidateEndpoints = false,
}
};
var disco = await client.GetDiscoveryDocumentAsync(req);
if (disco.IsError)
{
Console.WriteLine(disco.Error);
// output: Error connecting to http://localhost:8000/.well-known/openid-configuration/jwks. Object reference not set to an instance of an object..
return null;
}
// ...other codes
}
Edit:
I changed the code when config identity server and it works when running discovery.
var builder = services.AddIdentityServer(options =>
{
options.PublicOrigin = "https://10.2.5.90:8888";
});
However, I still couldn't access my api. The error is Exception occurred while processing message.IDX20803: Unable to obtain configuration from: 'http://localhost:8000/.well-known/openid-configuration' and I'm researchig the solution
You have to change de Authority property in your api. The api needs to access to the discovery endpoint through nginx (port 8888):
services.AddAuthentication("Bearer")
.AddJwtBearer(options =>
{
options.Audience = "api1";
options.RequireHttpsMetadata = true;
options.Authority = "https://10.2.5.90:8888";
});
If you have a public IP and your backend farm use internal uris you can adjust your host uri data in the middleware. Try to put this middleware in the first position:
app.Use(async (context, next) =>
{
context.Request.Scheme = "https";
context.Request.Host = new HostString("10.2.5.90.8888");
context.Request.PathBase = "yourApplicationPathBase";
await next.Invoke();
});
Of course, you have to parameterize this strings by environment in your configuration.
We have deployed the identity server4 as a aws serverless lambda function. But when we are calling the base authorize url from code(while creating token for client) getting the forbidden error. Here is the code:
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> Get(string client, string secret)
{
IActionResult result = null;
//discover endpoints from metadata
var disco = await DiscoveryClient.GetAsync("https://hide-for-security-reasons/prod/");
if (disco.IsError)
{
result = NotFound(disco.Error);
return result;
}
//request token
var tokenClient = new TokenClient(disco.TokenEndpoint, client, secret);
var tokenResponse = await tokenClient.RequestClientCredentialsAsync(scope: "sup");
if (tokenResponse.IsError)
{
result = NotFound(tokenResponse.Error);
}
result = Ok(tokenResponse.Json);
return result;
}
We are using Entity framework core with Identity server4 and getting the ArgumentOutOfRangeException when calling the token endpoint via code. Here is our ConfigurationServices method in Identity server project:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfiguration>(Configuration);
string connectionString = Configuration.GetConnectionString("IdentityServer");
var rsaProvider = new RSACryptoServiceProvider(512);
SecurityKey key = new RsaSecurityKey(rsaProvider);
var credentials = new Microsoft.IdentityModel.Tokens.SigningCredentials
(key, SecurityAlgorithms.RsaSha512Signature);
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
services.AddIdentityServer()
.AddSigningCredential(credentials)
// this adds the config data from DB (clients, resources)
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
}) // this adds the operational data from DB (codes, tokens, consents)
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
// this enables automatic token cleanup. this is optional.
// options.EnableTokenCleanup = true;
// options.TokenCleanupInterval = 30;
});
// Add S3 to the ASP.NET Core dependency injection framework.
services.AddAWSService<Amazon.S3.IAmazonS3>();
}
Here is our client application that calling identity server project("http://localhost:3000/"):
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> Get(string client, string secret)
{
IActionResult result = null;
//discover endpoints from metadata
var disco = await DiscoveryClient.GetAsync("http://localhost:3000/");
if (disco.IsError)
{
result = NotFound(disco.Error);
return result;
}
//request token
var tokenClient = new TokenClient(disco.TokenEndpoint, client, secret);
var tokenResponse = await tokenClient.RequestClientCredentialsAsync(scope: "sup");
if (tokenResponse.IsError)
{
result = NotFound(tokenResponse.Error);
}
result = Ok(tokenResponse.Json);
return result;
}
Here is error we have got: