nopCommerce Authentication using Identity Server4 Implementation Issue - authentication

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

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
}
},

IdentityServer4 redirects to Logout page instead of PostLogoutRedirectUri

I created AspNetCore 3.1 Project and added IdentityServer4 for SSO (added 'Microsoft.AspNetCore.ApiAuthorization.IdentityServer' package). Sig-nin works fine, but logout doesn't.
In Startup.cs I have the following configuration for IdentityServer :
public void ConfigureServices(IServiceCollection services)
{
...
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{ ...
var client = new Client
{
ClientName = "ssotestclient",
ClientId = "ssotestclient",
ClientSecrets = { new Secret("somesecret".Sha256()) },
AllowedGrantTypes = GrantTypes.Code.Union(GrantTypes.ResourceOwnerPasswordAndClientCredentials).ToArray(),
RequirePkce = false,
RequireClientSecret = false,
AllowOfflineAccess = false,
AlwaysSendClientClaims = true,
UpdateAccessTokenClaimsOnRefresh = true,
AlwaysIncludeUserClaimsInIdToken = true,
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess
},
RequireConsent = false,
RedirectUris = {"https://mytestsite.local/signin-oidc" },
PostLogoutRedirectUris = {"https://mytestsite.local/signout-callback-oidc"}
}
options.Clients.Add(client);
});
The client Website is an AspNetCore MVC project with 'Microsoft.AspNetCore.Authentication.OpenIdConnect' package.
Client initialization in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = "https://myssoserver.local";
options.RequireHttpsMetadata = false;
HttpClientHandler handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
options.BackchannelHttpHandler = handler;
options.ClientId = "ssotestclient";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.UsePkce = true;
options.Scope.Add("email");
options.Scope.Add("profile");
options.SaveTokens = true;
});
services.AddAuthorization();
services.AddRazorPages();
services.AddControllers();
}
Logout button on the client website has the following code :
public async Task OnPostAsync(string returnUrl = null)
{
await HttpContext.SignOutAsync("Cookies");
await HttpContext.SignOutAsync("oidc");
}
After this the browser is redirected to SSO server with the following location:
https://myssoserver.local/connect/endsession?post_logout_redirect_uri=https%3A%2F%2Fmytestsite.local%2Fsignout-callback-oidc&id_token_hint=<token>&state=<state>&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=5.5.0.0
Actual values of token and state are trimmed from this post because they are too long.
As you can see the post_logout_redirect_uri, id_token_hint and state parameters are passed to the server endsession endpoint.
And on the SSO server side I see the message that it has passed validation:
info: IdentityServer4.Validation.EndSessionRequestValidator[0]
End session request validation success
{
"ClientId": "sso_test_client",
"ClientName": "sso_test_client",
"SubjectId": "f3693d8c-6095-4f1a-9f8f-bdc7440e9395",
"PostLogOutUri": "https://mytestsite.local/signout-callback-oidc",
"State": "<state>",
"Raw": {
"post_logout_redirect_uri": "https://mytestsite.local/signout-callback-oidc",
"id_token_hint": "***REDACTED***",
"state": "<state>",
"x-client-SKU": "ID_NETSTANDARD2_0",
"x-client-ver": "5.5.0.0"
}
}
However after this request the browser receives 302 redirect to https://myssoserver.local/Identity/Account/Logout page instead of provided post_logout_redirect_uri.
And actual logout doesn't happen, because only OnGet() handler is called that does nothing
Could not find similar issue on the web.
What could be wrong? Why do I get redirected to the Logout page instead of a callback uri? I searched through the project code and could not find any reference with the 'Logout' word apart from Logout page itself
One thought is that you need to set the LogoutPath.
.AddCookie(options =>
{
options.LogoutPath = "/User/Logout";
})
LogoutPath is a security feature, just as this picture from one of my training classes shows:

identityserver4 Hybrid follow without competed login

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 = "/" });
}

Adding SignalR to existing IdentityServer4 Authorization

I have an Asp.Net core 2.2 IdentityServer4 application with a working system supporting claims, etc. I'm adding SignalR to it and want to use the [Authenitcation] header and have access to the same claims my controllers have.
I've found a couple of articles on integrating SignalR with IdentityServer4, but I can't tell what is overlap with things I'm already doing and what's necessary to add support for SignalR. Do I just need to inform IdentityServer of the specific SignalR route to authorize?
Here's a thorough article with an extensive example on GitHub:
https://mikebridge.github.io/articles/identityserver4-signalr/
https://github.com/mikebridge/IdentityServer4SignalR
I ended up re-working my IdentityServer4 usage to create a jwtbearer token and use the HybridAndClientCredentials and the User claims were picked up in my signalr session start event.
Add Hybrid Client to IdentityServer4:
new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
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 then in startup on the mvc client:
ServiceCollection.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "https://localhost:5001";
options.RequireHttpsMetadata = false;
options.ClientSecret = "secret";
options.ClientId = "mvc";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("offline_access");
options.Scope.Add("api1");
options.ClaimActions.MapJsonKey("website", "website");
});
This is modeled after the example: Quickstart5_HybridAndApi
In SignalR on server:
[Authorize]
public class SignalRSignalHub : Hub
{
public override Task OnConnectedAsync()
{
var context = Context.GetHttpContext();
return base.OnConnectedAsync();
}
}

WWW-Authenticate header in Identity Server 4 Unauthorized response

I'm protecting a Web API with Identity Server 4.
If an external app tries to access it using client credentials but does not pass in the access token, I get, as expected, the Unauthorized response.
The problem here is that the response does not include the WWW-Authenticate header as I was expecting, as stated in the OAuth spec.
Am I missing some config in Identity Server? Or is it something wrong with the Identity Server implementation?
The relevant code parts follow:
Client registration on Identity Server:
new Client()
{
ClientId = "datalookup.clientcredentials",
ClientName = "Data Lookup Client with Client Credentials",
AlwaysIncludeUserClaimsInIdToken = true,
AlwaysSendClientClaims = true,
AllowOfflineAccess = false,
ClientSecrets =
{
new Secret("XXX".Sha256())
},
AllowedGrantTypes = GrantTypes.ClientCredentials,
AllowedScopes =
{
Scopes.DataLookup.Monitoring,
Scopes.DataLookup.VatNumber
},
ClientClaimsPrefix = "client-",
Claims =
{
new Claim("subs", "1000")
}
}
ApiResource registration on Identity Server:
new ApiResource()
{
Name = "datalookup",
DisplayName = "Data Lookup Web API",
ApiSecrets =
{
new Secret("XXX".Sha256())
},
UserClaims =
{
JwtClaimTypes.Name,
JwtClaimTypes.Email,
JwtClaimTypes.Profile,
"user-subs"
},
Scopes =
{
new Scope()
{
Name = Scopes.DataLookup.Monitoring,
DisplayName = "Access to the monitoring endpoints",
},
new Scope()
{
Name = Scopes.DataLookup.VatNumber,
DisplayName = "Access to the VAT Number lookup endpoints",
Required = true
}
}
}
Authentication configuration in the Web API:
public void ConfigureServices(IServiceCollection services)
{
(...)
services.AddMvc();
services
.AddAuthorization(
(options) =>
{
options.AddPolicy(
Policies.Monitoring,
(policy) =>
{
policy.RequireScope(Policies.Scopes.Monitoring);
});
options.AddPolicy(
Policies.VatNumber,
(policy) =>
{
policy.RequireScope(Policies.Scopes.VatNumber);
policy.RequireClientSubscription();
});
});
services.AddAuthorizationHandlers();
services
.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(
(options) =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ApiName = "datalookup";
});
(...)
}
Client accessing the Web API:
using (HttpClient client = new HttpClient())
{
// client.SetBearerToken(accessToken);
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, Constants.WebApiEndpoint))
{
using (HttpResponseMessage response = await client.SendAsync(request).ConfigureAwait(false))
{
if (!response.IsSuccessStatusCode)
{
ConsoleHelper.WriteErrorLine(response);
return;
}
string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
ConsoleHelper.WriteInformationLine(content);
}
}
}
Notice that client.SetBearerToken(accessToken) is commented, so that is why I was expecting the response to include the WWW-Authenticate header.
The whole idea behind this is to implement a feature on a client library to deal with the Http Bearer challenge (as, for example, the Azure KeyVault client library does).