I'm using IdentityServer4 and trying to authenticate my Asp.Net Core 3.1 client manually (creating requests manually to understand the flow). Here is my client login:
[HttpGet]
public IActionResult ManualLogin()
{
var myNonce = Guid.NewGuid().ToString();
var myState = Guid.NewGuid().ToString();
var req = "https://localhost:5000/connect/authorize?" +
"client_id=mvc" +
"&redirect_uri=https://localhost:44381/signin-oidc" +
"&response_type=code id_token" +
"&scope=openid profile offline_access email" +
"&response_mode=form_post" +
$"&nonce={myNonce}" +
$"&state={myState}";
return Redirect(req);
}
this login method works fine and everything is ok but I don't want to use it:
//[HttpGet]
//public async Task LoginAsync()
//{
// await HttpContext.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties
// {
// RedirectUri = "https://localhost:44381/Home/external-login-callback"
// });
// }
my client's startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// for using IHttpClientFactory
services.AddHttpClient();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
// adding Authentication services to DependencyInjection
services.AddAuthentication(config =>
{
config.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
config.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, config =>
{
config.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
config.Authority = "https://localhost:5000";
config.ClientId = "mvc";
config.ClientSecret = "secret";
config.SaveTokens = true;
config.UseTokenLifetime = false;
// Hybrid Flow
config.ResponseType = "code id_token";
config.Scope.Add("openid");
config.Scope.Add("offline_access");
config.Scope.Add("profile");
config.Scope.Add("email");
config.GetClaimsFromUserInfoEndpoint = true;
config.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.GivenName,
RoleClaimType = JwtClaimTypes.Role,
};
// config.AuthenticationMethod = OpenIdConnectRedirectBehavior.FormPost;
});
services.AddControllersWithViews();
}
my Client's definition:
new Client
{
ClientId = "mvc",
ClientName ="My mvc client testing",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.Hybrid,
// where to redirect to after login
RedirectUris = { "https://localhost:44381/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "https://localhost:44381/signout-callback-oidc" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
},
AllowOfflineAccess = true,
UpdateAccessTokenClaimsOnRefresh = true,
AccessTokenType = AccessTokenType.Reference,
RequireConsent = false,
RequireClientSecret = true,
//AlwaysIncludeUserClaimsInIdToken = true,
RequirePkce = false,
}
My IS4's startup.cs:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
var builder = services.AddIdentityServer()
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients)
.AddTestUsers(TestUsers.Users);
builder.AddDeveloperSigningCredential();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
}
The client redirecting successfully to the IS4 login page, then I can authenticate the user, and when redirecting back to the signin-oidc
url on my client, I got the 500 Internal Server Error
:
Unable to unprotect the message.State.
Exception: Unable to unprotect the message.State.
Unknown location
Exception: An error was encountered while handling the remote login.
Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler<TOptions>.HandleRequestAsync()
Exception: An error was encountered while handling the remote login.
Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler<TOptions>.HandleRequestAsync()
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
I don't have multiple oidc, just one! What did I miss?
Update1: after @ToreNestenius comment:
I changed the redirect_uri of the request to:
&redirect_uri=https://localhost:44381/home/MyCallback"
and added the callback to the IS4 client's config, then here is my callback:
[HttpPost]
[ActionName("mycallback")]
public async Task mycallbackAsync(
string code,
string scope,
string state,
string session_state,
string login_required)
{
var theRequest = $"https://localhost:5000/connect/token";
var client = _httpClientFactory.CreateClient();
var theContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string,string>("client_id","mvc"),
new KeyValuePair<string,string>("client_secret","secret"),
new KeyValuePair<string,string>("grant_type","hybrid"),
new KeyValuePair<string,string>("code",code),
new KeyValuePair<string,string>("redirect_uri", "https://localhost:5002/home/mycallback"),
});
theContent.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
var base64StringUserPass = Convert.ToBase64String(Encoding.ASCII.GetBytes($"mvc:secret"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64StringUserPass);
var response = await client.PostAsync(req, theContent);
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
return;
}
var content = await response.Content.ReadAsStringAsync();
var theAccessToken = JsonConvert.DeserializeObject<Token>(content);
// -----------------------------
// get user info
//var access_token = theAccessToken.access_token;
//string userInfo = await getUserInfoAsync(access_token);
}
Now I can handle the callback correctly and then issue an accessToken and getting the userInfo.