identityserver4 Hybrid follow without competed login - asp.net-core

I have an API that needs to secure allowing 3rd party asp.net core MVC web application to request an access token and use this access token to request the secured API.
i created HybridAndClientCredentials client on the identity server
ClientId = "testclient",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
AllowOfflineAccess = true,
// secret for authentication
ClientSecrets =
{
new Secret("password".Sha256())
},
RedirectUris = {"http://127.0.0.1:55950",
"http://localhost/testLogin",
"https://localhost:44322/",
"https://localhost:44302/",
"https://localhost:44303/signin-oidc"},
RequireConsent = false,
// scopes that client has access to
AllowedScopes = { "roles" , IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile}
and MVC client as showing in identity server 4 documentation
.AddOpenIdConnect("oidc", options =>
{
options.Authority = Constants.Authority;
options.RequireHttpsMetadata = false;
options.ClientSecret = "password";
options.ClientId = "testclient";
options.ResponseType = "code id_token";
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
//options.Scope.Add("email");
//options.Scope.Add("resource1.scope1");
options.Scope.Add("offline_access");
options.ClaimActions.MapAllExcept("iss", "nbf", "exp", "aud", "nonce", "iat", "c_hash");
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role,
};
});
it's working but the user appears to be logged in the MVC application all I want is the access token to be used in calling the secured API also I don't want to use [authorize] attribute on the MVC client to redirect the user to identity server login page.

Something in the MVC application must to trigger a challenge to the OpenIDconnect handler, that starts the process to authenticate the user. As a result the user is logged in and you get access to the tokens.
Using a [Authorize] attribute is one way to trigger a challenge, or to do it manually using code like:
public async Task Login()
{
await HttpContext.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme,
new AuthenticationProperties() { RedirectUri = "/" });
}

Related

IdentityServer4 ADFS External Not Returning Roles

I have created an IdentityServer4 IDP using the standard template for Core Identity. I am looking to have an External provider being out ADFS 2016 Server. I have added this to the AddAuthentication() in Startup.cs
services.AddAuthentication()
.AddOpenIdConnect("adfs", "ADFS", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.Authority = "[AuthURL]";
options.ClientId = "[ClientId]";
options.ResponseType = "id_token token code";
options.Scope.Add("profile");
options.Scope.Add("email");
options.CallbackPath = "/signin-adfs";
options.SignedOutCallbackPath = "/signout-callback-adfs";
options.RemoteSignOutPath = "/signout-adfs";
options.ClaimActions.Add(new JsonKeyClaimAction("role", null, "role"));
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
});
It successfully redirects to the ADFS login window. Once signed in it calls the ExternalController.cs CallBack() correctly and I do have a successful authentication.
public async Task<IActionResult> Callback()
{
// read external identity from the temporary cookie
var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
if (result?.Succeeded != true)
{
throw new Exception("External authentication error");
}
The issue I am having is that I am not getting back the full user. I see claims but I do not see roles. I am used to seeing a JWT token which includes a list of Roles, however, I do not see these roles within the Result from above.
How can I either get a JWT token back from the Authentication against ADFS or have the roles returned and be within the Result?
Inside OIDC add scope for roles:
options.Scope.Add("roles");
Inside config.cs in IDS4 make sure your client has the allowed scopes:
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.OfflineAccess,
"roles",
},
If it's an API, make sure config.cs has the correct userclaims:
new ApiResource()
{
Name = "API",
DisplayName = "display",
ApiSecrets =
{
new Secret("secret".Sha256()),
new Secret
{
Value = "4A04D56554F731CCD123BB574D6918C8C83BDF65",
Type = "X509Thumbprint",
Description = "Certificate"
}
},
Scopes = new List<string>(){"postman Web API"},
Enabled = true,
UserClaims =
{
JwtClaimTypes.Name,
JwtClaimTypes.Email,
JwtClaimTypes.Subject,
JwtClaimTypes.Role,
JwtClaimTypes.Address,
JwtClaimTypes.Confirmation,
JwtClaimTypes.EmailVerified,
JwtClaimTypes.Id,
JwtClaimTypes.Profile
}
},

nopCommerce Authentication using Identity Server4 Implementation Issue

I am using NopCommerce 4.40.3 and Identity Server 4. I am trying to integrate Identity Server authentication with NopCommerce. I have followed the existing ExternalAuth.Facebook project approach and created the plugin. I am able to install and configure the plugin successfully. I am able to show the "Identity Server Authentication" button and when I click it is redirecting to Identity Server Login Page and I am able to authenticate to Identity Server with out any issues. I can able to see the claims and I can see the success message in the identity server console. But NopCommerce site still shows not logged in and when I try to access different pages in NopCommerce, It is redirecting me to login page.
After login, I am able to see the Identity Server cookies like "idsrv" and "idsrv.session". When I logout from Identity Server, I can see both cookies are cleared out.
My question is, how can I set successful login to NopCommerce site. Here's my implementation code. I don't know what I have missed in the configuration or in the implementation. Please help me.
Identity Server Client Configuration:
new Client
{
ClientName = "MiniApple.App.NopCommerce",
ClientId = "MiniApple.App.NopCommerce",
AllowedGrantTypes =GrantTypes.HybridAndClientCredentials,
RedirectUris = new List<string>{ "https://localhost:44369/signin-oidc" }, //Client Application Address
RequirePkce = false,
RequireConsent = true,
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Address,
IdentityServerConstants.StandardScopes.Email,
"MiniApple.API.Employee",
"roles"},
ClientSecrets = { new Secret("abcdefghijklmnopqrstuvwxyz".Sha512()) },
AllowAccessTokensViaBrowser = true,
AlwaysSendClientClaims = true,
PostLogoutRedirectUris = new List<string> { "https://localhost:44369/signout-callback-oidc" }
},
NopCommerce Authentication Registration:
public class IdentityServerAuthenticationRegistrar : IExternalAuthenticationRegistrar
{
/// <summary>
/// Configure
/// </summary>
/// <param name="builder">Authentication builder</param>
public void Configure(AuthenticationBuilder builder)
{
builder.AddOpenIdConnect("oidc", options => {
var settings = EngineContext.Current.Resolve<IdentityServerExternalAuthSettings>();
options.SignInScheme = "Cookies";
options.Authority = settings.Authority;
options.ClientId = settings.ClientKeyIdentifier;
options.ResponseType = settings.ResponseType;
options.SaveTokens = true;
options.ClientSecret = settings.ClientSecret;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add(settings.Scope);
options.Scope.Add("roles");
options.ClaimActions.MapUniqueJsonKey("role", "role");
options.TokenValidationParameters = new TokenValidationParameters
{
RoleClaimType = "role"
};
});
}
}
Identity Server Console Output:
info: IdentityServer4.Validation.TokenRequestValidator[0]
Token request validation success, {
"ClientId": "MiniApple.App.NopCommerce",
"ClientName": "MiniApple.App.NopCommerce",
"GrantType": "authorization_code",
"AuthorizationCode": "****F988",
"RefreshToken": "********",
"Raw": {
"client_id": "MiniApple.App.NopCommerce",
"client_secret": "***REDACTED***",
"code": "18978F1D183EDFA3E3F5918B85F43DDFEAFE74D49E207E2449F59A9490BFF988",
"grant_type": "authorization_code",
"redirect_uri": "https://localhost:44369/signin-oidc"
}
}
info: IdentityServer4.Hosting.IdentityServerMiddleware[0]
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.UserInfoEndpoint for /connect/userinfo
info: IdentityServer4.ResponseHandling.UserInfoResponseGenerator[0]
Profile service returned the following claim types: given_name family_name role
After successful login with identity server, it is redirecting to https://localhost:44369/signin-oidc and I am getting 404 error.
The issue is similar to the below
[question]: https://www.nopcommerce.com/en/boards/topic/60547/problems-making-custom-externalauth-plugin-for-openidconnect-to-auth0
Once I have changed the code as below, the authentication is works fine.
public void Configure(AuthenticationBuilder builder)
{
builder.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => {
//Set Open Id Parameters
var settings = EngineContext.Current.Resolve<IdentityServerExternalAuthSettings>();
options.Authority = settings.Authority;
options.ClientId = settings.ClientId;
options.ClientSecret = settings.ClientSecret;
options.ResponseType = settings.ResponseType;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add(settings.Scope);
options.Scope.Add("roles");
options.Scope.Add("openid");
options.Scope.Add("profile");
//options.Scope.Add("email");
options.SaveTokens = true;
options.ClaimActions.MapUniqueJsonKey("role", "role");
options.TokenValidationParameters = new TokenValidationParameters
{
RoleClaimType = "role"
};
options.Events = new OpenIdConnectEvents
{
OnRemoteFailure = context =>
{
context.HandleResponse();
var errorUrl = context.Properties.GetString(IdentityServerAuthenticationDefaults.ErrorCallback);
context.Response.Redirect(errorUrl);
return Task.FromResult(0);
}
};
});
}

Automatic login using jwt in Cookie in ASP.net MVC Core

My process flow is :
User logs into a Issuer Application (Username/Password)
Clicks a link of the Client Application that they want to goto
Issuer Application creates a jwt and stores it in a Cookie
Issuer Application does a Response.Redirect to Client Application
Client Application authenticates user using the jwt in the Cookie and creates the Principal and automatically logs in user.
Below is my Client Application setting from the Startup ConfigureServices method:
var key = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("password"));
SigningCredentials SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidIssuer = "issuerapp",
ValidateAudience = false,
ValidAudience = "clientapp",
ValidateIssuerSigningKey = true,
IssuerSigningKey = SigningCredentials.Key,
RequireExpirationTime = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.ClaimsIssuer = "issuerapp";
options.TokenValidationParameters = tokenValidationParameters;
options.SaveToken = true;
})
.AddCookie(JwtBearerDefaults.AuthenticationScheme,
options =>
{
options.Cookie.HttpOnly = true;
options.Cookie.Name = Configuration.GetValue<string>("AppSettings:CookieName");
options.AccessDeniedPath = authenticationSettings.AccessDeniedPath;
options.Events = new CookieAuthenticationEvents
{
// Check if JWT needs refreshed
OnValidatePrincipal = RefreshTokenMonitor.ValidateAsync,
OnSigningOut = (context) =>
{
context.HttpContext.Response.Redirect(Configuration.GetValue<string>("AppSettings:LogoutPath"));
return Task.CompletedTask;
},
};
});
In my Client Application I have all controllers decorated with [Authorize] attribute.
I need the Client Application automatically authenticating the user using the jwt. Which is not happening using the above mentioned configurations.
My AccessDeniedPath (Action Method) is not getting hit either.
The workaround that I have been using is to redirect user from the Issuer Applicaiton to a Login action in the Client Application where :
I read the jwt from the Cookie
Validate the jwt to get the Principal
Call httpContext.SignInAsync
How can I get the user logged in automatically using the jwt.
Any help / pointer are appreciated.
Thanks.
By default , the AddJwtBearer extension will get the token from request's Authorization header :
Authorization: Bearer <token>
But you are pass the token in cookie , so you can find the token in cookie and set token in OnMessageReceived event in AddJwtBearer :
options.Events = new JwtBearerEvents {
OnMessageReceived = ctx =>
{
ctx.Token = ctx.HttpContext.Request.Cookies["jwt"];
return Task.CompletedTask;
}
};

ASP.NET Core Identity uses wrong NameClaimType with OpenIdConnect

I'm using the OpenIdConnect middleware to do authentication with AzureAD. And Identity to handle all these.
In the scaffolded ExternalLogin page I receive a correct SignUpInfo where the principle name is as expected (Fullname). With the default implementation, Identity discard this information and uses the email address as the principle name. The respective principal name (Fullname) gets never persisted.
I've seen that these two uses different NameClaimTypes. But configuring it at AddIdentity(...) or AddOpenIdConnect(...) does not change anything.
How should I handle this?
Update
Startup.cs
var authenticationMethods = services.AddAuthentication()
.AddOpenIdConnect(authenticationScheme, authenicationDisplayName, options =>
{
options.ClientId = azureAdOptions.ClientId;
options.ClientSecret = azureAdOptions.ClientSecret;
options.Authority = $"{azureAdOptions.Instance}common/v2.0";
options.CallbackPath = azureAdOptions.CallbackPath;
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.SaveTokens = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = $"{azureAdOptions.Instance}{azureAdOptions.TenantId}/v2.0",
NameClaimType = "name"
};
options.Events.OnRemoteFailure = context =>
{
logger.LogError(context.Failure, "Fehler beim Anmelden via XYZ");
context.Response.Redirect("/");
context.HandleResponse();
return Task.CompletedTask;
};
azureAdOptions.Scopes.ForEach(options.Scope.Add);
});
// Fügt die ASP.NET CORE Identity Middleware hinzu
services.AddIdentity<TUser, IdentityRole>()
.AddRoleManager<RoleManager<TRole>>()
.AddDefaultUI()
.AddDefaultTokenProviders()
.AddEntityFrameworkStores<TContext>();
ExternalLogin.cshtml.cs (default scaffolded code)
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor : true);
if (result.Succeeded)
{
await _signInManager.UpdateExternalAuthenticationTokensAsync(info);
_logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider);
return LocalRedirect(returnUrl);
}
Here, the info.Principal.Identity.Name represents the readable fullname which has ben derived from the NameClaimTypes 'name' from OpenIdConnect. But after the login, the Identity uses 'XML-Namespace' for NameClaimTypes and in each Controller the User.Identity.Name resolves to the username / email address.

asp.net mvc login from identityserver4 with a invalid_request error

today i use the demo of identityserver4 Build a validation server, and i can use the asp.net core client with openid login the client.
but i could not login my asp.net mvc5 client with openid, The error of the prompt is : invalid_request,
here is my identityserver4 config code with getclient()
// clients want to access resources (aka scopes)
public static IEnumerable<Client> GetClients()
{
// client credentials client
return new List<Client>
{
// OpenID Connect hybrid flow and client credentials client (MVC)
new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
RequireConsent = true,
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { "http://localhost:5002/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
},
AllowOfflineAccess = true
}
};
}
}
and the follow code is my asp.net mvc5 clent ConfigureAuth(),because the idenetiyServer4 define the ClientSecrets is "secret".Sha256(),so in this mvc client , i set the ClientSecret = GetSHA256HashFromString("secret"),i create prvate the method GetSHA256HashFromString() to convert the string to sha256.
here is my code:
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
SignInAsAuthenticationType = "Cookies",
Authority = "http://localhost:5000", //ID Server SSO Server
ClientId = "mvc",
ClientSecret = GetSHA256HashFromString("secret"),
ResponseType = "code id_token",
RedirectUri = "http://localhost:5002/signin-oidc", //URL of Client website
PostLogoutRedirectUri = "http://localhost:5002/signout-callback-oidc", //URL of Client website
Scope = "api1",
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
RequireHttpsMetadata = false,
});
and i press f5 to run the mvc client ,and press the button of login,the brower can jump to the localhost:5000,but it is give me a error:
Sorry, there was an error : invalid_request and the other error info are :
Request Id: 0HL9RHBTJIT3T:00000003**
thanks a lot.
The value of ClientSecret should be the actual secret value not the hashed one.
The secret is stored as hash when you use a persisted data storage to prevent an attacker to obtain your client's secrets in case if your storage is compromised.
In your case, The secret value is "secret". So the code will be
ClientSecret = "secret"