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.
Related
I want to implement an authentication system in ASP .NET Core where:
The user clicks a button which looks like the standard Google sign-in button.
The user is then prompted to sign in to Google Accounts and signs in.
A http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier claim with the value equal to that of the user_id of the signed-in Google account is added to the User variable in the RazorBasePage class.
The server adds the user to a user table with user_id as the primary key.
I originally investigated a solution using the built-in ASP .NET Identity system. However, I soon realised it was far more functionality than what I needed.
Next, I followed this article to implement a system where the user must authenticate with their Google account when attempting to use controllers or actions tagged with the [Authorize] attribute.
Meanwhile, I also investigated a login system using this article. This article implements a system where developer can implement their own custom authorisation system, e.g. check against a hard-coded password.
And I also investigated some of Google's developer pages on identity This system allows the developer to easily implement an authentication system on the client side - additional steps are required to pass the authentication to the server.
This collection of images should help to communicate the aforementioned authorisation systems. My current ConfigureServices method in StartUp.cs contains the following code:
services.AddAuthentication(options => {
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme;
})
.AddCookie()
.AddGoogle(options => {
options.ClientId = Configuration["Authentication:Google:ClientId"];
options.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.SaveTokens = true;
});
Any tips on how to implement such a system would be greatly appreciated. Thanks!
Looks like Google deprecated use of Google+ for retrieving user information:
https://github.com/aspnet/AspNetCore/issues/6486
In ASP.Net Core MVC 2.0 I ended up doing this in Startup.cs:ConfigureServices
services.AddAuthentication(options => {
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme;
})
.AddCookie()
.AddGoogle(options => {
options.ClientId = Configuration["Authentication:Google:ClientId"];
options.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
options.SaveTokens = true;
options.UserInformationEndpoint = "https://www.googleapis.com/oauth2/v2/userinfo";
options.ClaimActions.Clear();
options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
options.ClaimActions.MapJsonKey(ClaimTypes.Name, "name");
options.ClaimActions.MapJsonKey(ClaimTypes.GivenName, "given_name");
options.ClaimActions.MapJsonKey(ClaimTypes.Surname, "family_name");
options.ClaimActions.MapJsonKey("urn:google:profile", "link");
options.ClaimActions.MapJsonKey(ClaimTypes.Email, "email");
options.ClaimActions.MapJsonKey("picture", "picture");
})
;
Don't forget to add the following line before app.UseMvc()
app.UseAuthentication();
Also, you will need to configure Google API for cloud identity for your app.
To display the information you can do something like:
#Context.User.Identity.Name
<img src="#Context.User.Claims.SingleOrDefault(c => c.Type == "picture")?.Value" />
Finally: Please consider privacy of this information. It's not ethical (and in most jurisdictions not legal) to store private information without telling the user that you are storing it and for what purpose.
Use blow code get user data.
services.AddAuthentication(options => {
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme;
})
.AddCookie()
.AddGoogle(options => {
options.ClientId = Configuration["Authentication:Google:ClientId"];
options.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.SaveTokens = true;
options.UserInformationEndpoint = "https://openidconnect.googleapis.com/v1/userinfo";
options.ClaimActions.Clear();
options.ClaimActions.MapJsonKey(ClaimTypes.PPID, "ppid");
options.ClaimActions.MapJsonKey(ClaimTypes.Name, "email");
});
This all data you get on your Callback method into controller with in Claim type and value.
If you want to get other into than added your key like options.ClaimActions.MapJsonKey(ClaimTypes, jsonKey)
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.
Haven't found any really helpful articles or SO questions regarding this problem.
I want to behave my API in the following way:
All controller actions should only be available for cookie authenticated users
There are 2 login actions for getting a cookie by form (username/pw) or sso with the windows user (this is rather easy to do)
My problems with this currently:
When the API returns 401 (the correct status code) for a controller action, the browser automatically defaults to re-triggering the request with a NTLM token. The user doesn't have a cookie and is still able to call the API action because the windows authentication scheme is still registered somehow
It works as I want it to when I decorate ALL of the API actions with:
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
In this case it seems that the Windows Authentication Scheme is never used but only cookies, which is what I want.
I want this to be the only allowed behavior though without having to decorate all of the actions explicitly and never allow Windows Authentication Scheme (apart from the windows login action)
I can also kind of getting it working when I make sure that the Cookie Authentication doesn't return 401 but returns 403, which doesn't make the browser to re-trigger the request with NTLM authentication. This is a workaround at best though.
The relevant parts of Startup.cs currently look like this:
services.AddAuthentication(opt =>
{
opt.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(ConfigureApplicationCookie);
Cookie config:
private static void ConfigureApplicationCookie(CookieAuthenticationOptions options)
{
options.Cookie.Name = Assembly.GetEntryAssembly().GetName().Name;
options.Cookie.HttpOnly = true;
options.Cookie.Expiration = TimeSpan.FromHours(1);
options.SlidingExpiration = true;
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return Task.CompletedTask;
};
options.Events.OnRedirectToAccessDenied = context =>
{
context.Response.StatusCode = StatusCodes.Status403Forbidden;
return Task.CompletedTask;
};
options.LogoutPath = null;
options.LoginPath = null;
}
IIS (and IIS express) is configured to use Anonymous and Windows Authentication.
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.
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.