I have two clients requiring authentication.
One client is a spa that uses implicit flow, and the other is a direct system integration that uses the client credentials flow for login.
For some reason, when my client credentials client calls my API, my Identity Server app tries to call the .well_known/openid-configuration endpoint on itself.
The call makes no sense, seeing that it is the server which is serving the configuration in the first place that is trying to call an endpoint in itself.
Is there a way to populate this configuration without having identity server call its own endpoint?
Below is a snippet with my Identity server configuration.
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "Bearer";
options.DefaultChallengeScheme = "oidc";
}).AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = openIdConnectConfig.SignInScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.Authority = openIdConnectConfig.Authority;
options.RequireHttpsMetadata = false;
options.ClientId = clientConfig.First(x => x.ClientId == "spa_app").ClientId;
options.SaveTokens = true;
options.SignedOutRedirectUri = "http://localhost:8080";
}).AddIdentityServerAuthentication(options =>
{
options.Authority = openIdConnectConfig.Authority;
options.RequireHttpsMetadata = false;
options.ApiName = "api_client";
});
It's not possible to prevent this behaviour (at least not unless you attempt to implement the IConfigurationManager<OpenIdConnectOptions>). This is actually an intended behaviour because you have your web app & identity server hosted on the same app. The call to its own endpoint is due to the AddOpenIdConnect authentication scheme which when you start up the app will fetch the identity provider metadata information for JWT validation purposes.
You could theoretically go and implement IConfigurationManager<OpenIdConnectOptions> that does not call the MetadataAddress endpoint and set that in the authentication scheme builder.
.AddOpenIdConnect("oidc", options =>
{
...
ConfigurationManager = myCustomConfigurationManager, //You would need to implement this
...
})
This is the culprit that's responsible for the call to the MetadataAddress endpoint which by default is authorityUri + /.well_known/openid-configuration.
I would advise against doing so because in the end you will need the identity provider metadata information anyway so would have to snapshot and store it locally or something like that.
Related
Say I have a setup like this:
MVCApp1
MVCApp2
Identity Server
Therefore there are three projects inside my solution. Identity Server is now working for all of them. However, I am finding that I have to login to each one individually. Is that correct? i.e. if I login to MVCApp1, then does that mean I should also be implicitly logged in to MVCApp2?
Say I wanted to login to all three web apps, then would I have to browse to each web app and login or should I only have to do this once (I thought this is what single sign on was for).
Here is some code:
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = identityUrl;
options.RequireHttpsMetadata = false;
options.ClientId = "mvc2";
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("API1");
options.Scope.Add("API2");
options.Scope.Add("offline_access");
});
SSO is designed to handle this case, and no, you shouldn't need to login to each application individually.
If a user is not logged in, you should redirect them to the login page, when they can authenticate with the Identity Server. Once authenticated, the user should be able to access (without login) to both applications MVCApp1 and MVCApp2.
I would recommend storing your JWT's in a cookie, which can then be shared by your applications IF they live under the same domain. Then when any of your applications require authorization, get the JWT from the cookie in the request header and use that for authentications.
I am having some issues after making some tweaks to an IdentityServer4 Quickstart sample solution, specifically the 8_AspNetIdentity sample.
I'll preface this by saying I'm not sure if what I'm trying to do is just not supported, or if I'm doing it wrong.
This sample solution contains the following projects relevant to my question:
an IdentityServer,
an MVC client (named MVCClient) that uses OpenIdConnect to authenticate its users,
a web API client (named API) that uses bearer authentication for its users
a console app (named ResourceOwnerClient) designed to be a client of the API
What I am trying to do is merge the API project into the MVCClient, so that the MVCClient could both authenticate the users from its MVC website with OIDC, and also the ResourceOwnerClient using bearer authentication.
I made the following changes to the MVCClient's Startup.cs:
changed services.AddMvc(); to:
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder(new[]
{
JwtBearerDefaults.AuthenticationScheme,
CookieAuthenticationDefaults.AuthenticationScheme,
"oidc"
})
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
added JWT bearer options to the services.AddAuthentication():
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.Audience = "api1";
})
Now technically this did work, as both the ResourceOwnerClient and the MVC users can successfully authenticate with the MVCClient. I however have one caveat:
When I authenticate with a user from the MVC side, I noticed that there are two identities in my current User. Both are identical in terms of claims, etc. This only happens when I put a breakpoint in the MVCClient, on the IdentityServer there is only one identity.
On the IdentityServer, I have registered a UserClaimsPrincipalFactory which adds my own custom claims to the ClaimsIdentity. In the two identities on the IdentityServer, I can see the claims duplicated. So instead of having one identity with two custom claims, I see two identities which each have 4 custom claims. The CreateAsync method in my UserClaimsPrincipalFactory is also getting hit 5 times for a single login.
Although this behaviour is strange, it does not seem to be having any negative impacts. But this is only a proof of concept for a larger application that I'm building, and I'm afraid I may run into issues in the future because of it.
If anyone has attempted this sort of thing before, or knows why this behaviour could be happening, any help would be appreciated.
While nothing bad should happen with this design, I would completely remake it. Why? Because you are mixing a Client and an ApiResource, and they should be logically separated. A Client is an application, something some user interacts with, even if it was a headless one (i.e an automated service); while an ApiResource consists of resources that are provided to Clients, so no user can interact with it directly.
You could add two authentications against IdentityServer, one as API (and add it as JwtBearer) and one as a Client (and add it as Cookies). You can then use [Authorize(AuthenticationSchemes = "JwtBearer")] and = "Cookies" depending on the function of that Action/Controller.
Leaving that aside, the problem is that your application is getting one Identity for the MVC side and one for the API side, since it has no way of telling which one you want.
Just so you have an idea, this is how one of my IdentityServers with ASP.NET Core Identtiy look like, where you can login against it using the UI and also hit the REST endpoints with a JwtToken:
services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddIdentityServerAuthentication(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Authority = Configuration["IdentityServerUrl"];
options.ApiName = Configuration["ApiName"];
options.RequireHttpsMetadata = false;
})
.AddCookie(IdentityConstants.ApplicationScheme, o =>
{
o.LoginPath = new PathString("/Account/Login");
o.Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
};
})
.AddCookie(IdentityConstants.ExternalScheme, o =>
{
o.Cookie.Name = IdentityConstants.ExternalScheme;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5.0);
})
.AddCookie(IdentityConstants.TwoFactorRememberMeScheme, o =>
{
o.Cookie.Name = IdentityConstants.TwoFactorRememberMeScheme;
})
.AddCookie(IdentityConstants.TwoFactorUserIdScheme, o =>
{
o.Cookie.Name = IdentityConstants.TwoFactorUserIdScheme;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5.0);
});
I've set up the following authentication in my MVC client.
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(_ =>
{
//_.DefaultScheme = "Cookies";
//_.DefaultChallengeScheme = "oidc";
_.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie("Cokies")
.AddFacebook()
.AddGoogle()
.AddJwtBearer()
.AddOpenIdConnect("oidc", _ => { });
Since the default is set to JWT, I imagine that the others (Cookie, Facebook, Google and OpenIdConnect) are simply disregarded if the authorization decorator is invoked with no parameters.
However, I simply get 401 Unauthorized with no additional information. For instance, if I comment out the default scheme statement for JWT and activate the cookie one like this:
{
_.DefaultScheme = "Cookies";
_.DefaultChallengeScheme = "oidc";
//_.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}...
I get developer error page saying that client ID is unknown. But in this case, I only get the default page in Chrome with the error code. I was considering that maybe the default challenge scheme needs to be set but it makes no sense to do that for JWT, right? Also, I see no field for that in JwtBearerDefaults class.
What am I missing in my MVC client to make it authenticate properly? Or is the issue in my configuration of the IDS4?
I've googled for a while but I'm not getting any hits that talk to me and that I'd recognize as meaningful. Might be my ignorance, though...
A shorter version still producing the same issue looks like this.
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(_ =>
{
_.Authority = "https://localhost:44300";
_.Audience = "http://localhost:5001";
});
My IDS4 runs on port 44300 (checked with .well-known) and the API is on port 5001.
I am creating a sample application to just to understand how identity server 4 authentication works with Asp.net core 2. I have noticed some cookies are generated for different levels as it can be seen in the attached screenshot. My problems is why these cookies are generated?
Below statement, I take it from the Identity Server document. When identity server is configuring
IdentityServer internally calls both AddAuthentication and AddCookie with a custom scheme (via the constant IdentityServerConstants.DefaultCookieAuthenticationScheme),
Here why it calls AddCookies method on identity server itself?
Also when I configure Asp.net core web client to use Identity server authentication it also call AddCookie() method. When I try to comment it It will give me an error. I am bit of unclear what is happening here.
Identity Server Configurations
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddToDoUserStore()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients());
services.AddAuthentication("MyCookie")
.AddCookie("MyCookie", options =>
{
options.ExpireTimeSpan = new System.TimeSpan(0, 0, 15);
});
Web Client Configuration
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.Authority = "https://localhost:44377/";
options.RequireHttpsMetadata = true;
options.ClientId = "ToDoTaskManagmentClient";
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("address");
options.Scope.Add("roles");
options.Scope.Add("usertodoapi");
options.Scope.Add("countries");
options.Scope.Add("subscriptionlevel");
options.Scope.Add("offline_access");
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.ClientSecret = "secret";
options.GetClaimsFromUserInfoEndpoint = true;
options.ClaimActions.Clear();
options.ClaimActions.MapJsonKey("given_name", "given_name");
options.ClaimActions.MapJsonKey("family_name", "family_name");
options.ClaimActions.MapJsonKey("role", "role");
options.ClaimActions.MapJsonKey("country", "country");
options.ClaimActions.MapJsonKey("subscriptionlevel", "subscriptionlevel");
options.Events = new OpenIdConnectEvents()
{
OnTokenValidated = e =>
{
var identity = e.Principal;
var subjectClaim = identity.Claims.FirstOrDefault(z => z.Type == "sub");
var expClaims = identity.Claims.FirstOrDefault(z => z.Type == "exp");
var newClaimsIdentity = new ClaimsIdentity(e.Scheme.Name);
newClaimsIdentity.AddClaim(subjectClaim);
newClaimsIdentity.AddClaim(expClaims);
e.Principal = new ClaimsPrincipal(newClaimsIdentity);
return Task.FromResult(0);
},
OnUserInformationReceived = e =>
{
e.User.Remove("address");
return Task.FromResult(0);
}
};
});
Your Identity Server application needs an authentication cookie (and session ID cookie) so that the front channel endpoints (authorize, consent, check_session_iframe and possibly others) know if the user is authenticated or not and the current state of the session. Without this it would have no idea who was calling it. IDS4 will automatically redirect to the login URL of the default scheme if it detects that the incoming request is not authenticated - you are then free to implement any authentication flow you like.
Your client applications may or may not need cookies depending on the architecture. A traditional server side WebForms or MVC-style app will need one but a pure JS client using a library like oidc-client-js will not and can talk to the back-end purely using the access token obtained from your identity server.
IdentityServer doesn't do any of this. All it does is handle the low-level authentication/authorization and return a claims principal. Your application that's using IdentityServer is the one that would set the cookie.
What you're doing here is essentially having the same app host both IdentityServer and a cookie auth-based frontend. The cookie portion is for the traditional login flow UI, so that the app can recognize whether the user is authenticated and redirect to a login form or to an account page or back to the originating app, if or when they are authenticated.
That piece could be completely spun-off into a totally different app, and then your IdentityServer app would no longer need the cookie auth config.
So I need to AddOpenIdConnect in order to declare my SignInScheme and Scopes.
Any idea how to do this in a WPF App? And where should this method be called?
services.AddOpenIdConnect(options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; // cookie middle setup above
options.Authority = "http://localhost:5000"; // Auth Server
options.RequireHttpsMetadata = false; // only for development
options.ClientId = "native.code"; // client setup in Auth Server
options.ResponseType = "token";
options.Scope.Add("fiver_auth_api");
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
});
It`s my first time working with Desktops Apps.
Check out this library and associated samples. Should give you everything you need:
https://github.com/IdentityModel/IdentityModel.OidcClient2
The recommended way to handle OIDC sign ins in a desktop app is to use the user's default browser and either a custom URL scheme or local HTTP listener to receive the response.