When using AddOpenIdConnect, why are default Scopes added? - c#

I am trying to set-up an OpenIdConnect within my Startup.cs class of my .NET Core project but I keep being presented with the error:
Message contains error: 'invalid_scope', error_description: 'error_description is null', error_uri: 'error_uri is null'.
I guess the reason for this error is because during my code shown below, I clear the o.Scope list because it seems to contain 2 default scopes:
openid
profile
If I do NOT clear the default scopes, the client I am accessing is unable to recognise my request against a valid application, and therefore my request fails at their side. In this case, I do re-add the scope: "openid" which allows me to make a successful request to my client, but then on the redirection to my RedirectUri I am getting the error mentioned above.
Is the "invalid_scope" error coming from my client? Or is this occurring within the Middleware using by .NET Core?
Should I ask the client to update the scopes at their side to include "profile" so I don't have to clear the default scopes?
services.AddAuthentication().AddOpenIdConnect(socialProvider.ProviderName, o =>
{
o.ClientId = "xx"
o.ClientSecret = "xx"
o.Authority = "xx"
o.CallbackPath = "xx"
// There appear to be 2 scopes added by default here that cause the
// integration for o to not be recognised as an application when we get to o' side.
o.Scope.Clear();
o.Scope.Add("openid");
o.GetClaimsFromUserInfoEndpoint = true;
o.SaveTokens = true;
o.RequireHttpsMetadata = false;
});

AddOpenIdConnect does auto create the OpenIdConnectOptions object, and the default scope is openid and profile. So you should either tell your identity to support both of these scopes or clear our the default scope and add whatever the client is supported

Related

Why are .NET 6 Application + Application Gateway + Open ID Connect - Path based routing on different app services not working

I have an application gateway set up ("gateway"):
apps.mydomain.com
I have an app service set up ("app"):
my-app-service.azurewebsites.net
The path based rule is set on the listener for on the gateway address above.
/apps/app1/*
The default backend target and settings are set to the root of the gateway address above.
I am using AADS as the authentication store.
Both work correctly independently as I have another route set up on the gateway. I can go to the app service and it will prompt me for credentials, then take me to the index page at the root.
my-app-service.azurewebsites.net/
What I am trying to do is set up a path based rule that routes through the gateway and lands on a path under apps.mydomain.com. For example,
apps.mydomain.com/apps/app1.
I have set up the gateway properly as I can get to a static page. For example,
apps.mydomain.com/apps/app1/somedirectory/mystaticpage.html.
My problem is that when I try to authenticate, I think the signin-oidc is routing the request incorrectly. I am able to authenticate, and it appears to pass back to apps.mydomain.com/apps/app1/signin-oidc and then the middleware passes back to the root. It is authenticating, because when it hits the error page, it shows me as logged in.
I have tried overriding the cookie policy options:
builder.Services.Configure<CookiePolicyOptions>(options =>
{
options.Secure = CookieSecurePolicy.SameAsRequest;
options.MinimumSameSitePolicy = SameSiteMode.None;
//options.HttpOnly = Microsoft.AspNetCore.CookiePolicy.HttpOnlyPolicy.None;
});
I have tried listening to the OnRedirectToIdentityProvider:
builder.Services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
//options.CallbackPath = new PathString("/apps/app1/");
//options.CallbackPath = new PathString("/apps/app1/signin-oidc");
//options.CallbackPath = "/apps/app1/signin-oidc";
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = (context) =>
{
//https://stackoverflow.com/questions/50262561/correlation-failed-in-net-core-asp-net-identity-openid-connect
context.Options.NonceCookie.Path = "https://apps.mydomain.com/apps/app1/signin-oidc";
context.Options.CorrelationCookie.Path = "https://apps.mydomain.com/apps/app1/signin-oidc";
//https://learn.microsoft.com/en-us/azure/frontdoor/front-door-http-headers-protocol#front-door-to-backend
context.ProtocolMessage.RedirectUri = "https://apps.mydomain.com/apps/app1/signin-oidc";
return Task.FromResult(0);
}
};
});
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
builder.Services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the default policy.
options.FallbackPolicy = options.DefaultPolicy;
});
My guess is that just setting the otions.CallbackPath should work, but I just get correlation or sorry, we cannot log you in errors when I try that. Not sure if there is an error in the library.
I have spent over a month on and off and engaged MS technical support trying to solve this, but have not been able to get this to work. I can't imagine I am the only one doing this. I know it is in the open ID connect middleware somewhere, but cannot find the correct combination.
This is just a demo project in .NET 6 to get this working correctly. Any code will do. If there is actual working code somewhere that would be great. Just need to get the path based routing with authentication to work.

How to set OpenIdConnect Client Authentication Method (from a config as a parameter) in C# using Microsoft.AspNetCore.Authentication?

Given an OIDC implementation supporting the major id providers like Google, Okta, Azure, OneLogin etc. but now I run into a problem. A client of ours wants to use their own custom IDP, which only supports client_secret_basic as a Client Authentication method as their IDP says in their meta json:
"token_endpoint_auth_methods_supported": [
"client_secret_basic"
],
As I learned, OIDC standard defines the followings:
"client_secret_basic","client_secret_post","client_secret_jwt","private_key_jwt","none"
But seemingly, we "do not support" client_secret_basic as they got this error:
Message contains error: 'invalid_client', error_description: 'client authentication failed', error_uri: 'error_uri is null'.
which I learned to indicate a problem with client authentication (method). Also, when I tried to switch the Authentication Method setting for OneLogin IDP in our dev environment as you see here
I got the same error message as our partner.
When browsing options for setting up AuthenticationBuilder I found something called AuthenticationMethod:
// ...
foreach (var idp in idProviders)
{
builder.AddOpenIdConnect(idp.Name, idp.DisplayName, options =>
{
mapper.Map(idp.Options, options);
options.ResponseType = OpenIdConnectResponseType.Code;
options.ResponseMode = OpenIdConnectResponseMode.Query;
options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet; // test line added ("hardcoded") by me
// ...
It is an enum:
public enum OpenIdConnectRedirectBehavior
{
RedirectGet = 0,
FormPost = 1
}
Its name AuthenticationMethod seems to be similar, albeit I don't think it's the correct option as
it doesn't solve the problem
it has only 2 options
the values of enum don't match exactly the names in the OIDC standard (like client_secret_basic)
its default already RedirectGet:
public OpenIdConnectRedirectBehavior AuthenticationMethod { get; set; } = OpenIdConnectRedirectBehavior.RedirectGet;
whereas OneLogin works with POST but not with GET.
So, how can I change the client authentication method (and still keep supporting all standard IDPs - hence the config parameter)?
Thank you!
It seems like Microsoft does not support the "client_secret_basic" authentication method. At least OneLogin says so:
https://developers.onelogin.com/blog/how-to-use-openid-connect-authentication-with-dotnet-core :
"Dotnet OpenId Connect libraries do not support the Basic method of authentication."

WebAPI making an extra trip for user claims using OIDC authentication handler

My Current Setup is:
I have an Identity server built using Duenede.IdentityServer package running at port 7025.
I have a WebApi which is Dotnet 6 based and below is its OIDC configuration.
AddOpenIdConnect("oidc", o =>
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
o.SaveTokens = true;
o.GetClaimsFromUserInfoEndpoint = true;
o.RequireHttpsMetadata = false;
o.ResponseType = "code";
o.Authority = "https://localhost:7025/";
o.ClientId = "some clientid";
o.ClientSecret = "some secret";
o.Scope.Clear();
o.Scope.Add("openid");
o.Scope.Add("profile");
o.Scope.Add("dotnetapi");
o.NonceCookie.SameSite = SameSiteMode.Unspecified;
o.CorrelationCookie.SameSite = SameSiteMode.Unspecified;
o.ClaimActions.MapUniqueJsonKey("role", "role");
o.ClaimActions.MapUniqueJsonKey("email", "email");
});
Now when web api request the token from the identityserver (OIDC is the challenge scheme and i have a cookie scheme set as default authentication scheme) it gets both id_token and access_token(verified using await HttpContext.GetTokenAsync("access_token"); await HttpContext.GetTokenAsync("id_token");). I can also find user claims in HttpContext.User.FindFirst("some claim");
But i have noticed that there is an extra call to the identity server from web api for the userinfo. I observed that it may be because of o.GetClaimsFromUserInfoEndpoint = true; when i omitted this line i found that user claims are not set, even though i am still getting both id and access token.
So my understanding is the OIDC client of dotnet is using userinfo endpoint to fetch the user claims. But my question is if i am already receiving the access_token why there is an extra call for the userinfo. Can this extra call be prevented?
is there any way so that i receive id_token at first and access_token is then fetched as it is doing now so that same information is not sent twice?
First, you can set this client config in IdentityServer to always include the user claims in the ID token
AlwaysIncludeUserClaimsInIdToken
When requesting both an id token and access token, should the user
claims always be added to the id token instead of requiring the client
to use the userinfo endpoint. Default is false.
The reason for not including it in the ID-token is that increases the size of the id-token and if you store the tokens in the asp.net session cookie, it also can become pretty big.
I wouldn't worry about the extra request that happens when the user authenticates.

Identity Server 4 - unauthorized client

I am struggling with basic setup of the Identity Server 4 with Net Core 3.0 and React (but this is almost irrelevant).
I have generated new app by dotnet new react -au Individual, updated dependencies etc, Created config basically copied from the demo server with the following:
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
// JavaScript Client
new Client
{
Enabled = true,
ClientId = "spa",
ClientName = "SPA (Code + PKCE)",
RequireClientSecret = false,
RequireConsent = false,
RedirectUris = { "https://notused" },
PostLogoutRedirectUris = { "https://notused" },
AllowedGrantTypes = GrantTypes.Code,
AllowedScopes = { "openid", "profile", "email", "api" },
AllowOfflineAccess = true,
RefreshTokenUsage = TokenUsage.ReUse
},
};
}
In my startup:
services.AddDefaultIdentity<ApplicationUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer(o =>
{
o.UserInteraction.ErrorUrl = "/myErrorsHandler";
o.Events.RaiseErrorEvents = true;
o.Events.RaiseFailureEvents = true;
o.Events.RaiseInformationEvents = true;
o.Events.RaiseSuccessEvents = true;
})
.AddInMemoryApiResources(Config.GetApis())
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>()
.AddInMemoryClients(Config.GetClients()) ;
Then I am trying in Postman:
and always getting:
{"displayMode":null,"uiLocales":null,"error":"unauthorized_client","errorDescription":"Unknown client or client not enabled","requestId":"0HLPL86NBMDRG:00000001","redirectUri":null,"responseMode":null,"clientId":"spa"}
I really don't understand why this is not working.
The same client on demo server with the same in Postman dialog works without any issues.
UPDATE:
I found this docs: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity-api-authorization?view=aspnetcore-3.0#application-profiles
but I am still not able to get it working.
It recognizes the client, but despite the config (SPA, IdentityServerSPA) throwing:
{"displayMode":null,"uiLocales":null,"error":"invalid_request","errorDescription":"code challenge required","requestId":"0HLPL8VD22382:00000001","redirectUri":"http://localhost:5000/authentication/login-callback?error=invalid_request&error_description=code%20challenge%20required#_=_","responseMode":"query","clientId":"spa"}
UPDATE 2:
It is "working" with client defined in configuration JSON but only with predefined templates as per doc, but it is impossible (or possibility is not documented) to disable PKCE to make it work e.g. with Postman etc.
You're not defining the client_secret. Based on the code you've provided on the client's configuration you did not setup a client secret, so If no client secret is specified, there's no direct way for your client to prove its authenticity to your Authority (IDserver). This is when PKCE comes in handy, at least you can guarantee that same system is doing both requests.
I see you're asking to disable PKCE, that should not be possible (I'm not sure if it can be done but you definitely shouldn't do that) because you're using code authentication grant for an SPA. (which is the current recommended way of doing things)
As an SPA is a non-confidential client (uncapable of keeping a secret secure) this means that any other application could use your client_id spa to make requests to the token endpoint. To prevent this we combine two things:
Redirect URI: this enforces the response code token to be redirected to a previously known address which should be your client (unless using hosts file to suplant your site)
PKCE: a mechanism that aims to guarantee that both /authorize and /token requests come from the same client, so even if someone manages to intercept the code, he/she should not be able to use it in exchange for a token, because not knowing the original secret used in PKCE.
I struggled with the unauthorized_client error for the Resource Owner Password Validation flow in IdentityServer 4 because the grant type password was missing in [dbo].[ClientGrantTypes] for the associated client_id.
I had to insert a new line into the table to fix this error.
INSERT INTO [dbo].[ClientGrantTypes] (
[GrantType]
,[ClientId])
VALUES ('password', X) --where X is value of [dbo].[Clients].[Id] of used client

Facebook C# SDK OAuth Exception "ClientID required"

This question is, I think, similar to my previous one.
Using the latest C# Facebook SDK on .NET 4 I get an Exception with the message "ClientID required" with the following code on the last line:
var app = new DefaultFacebookApplication();
app.AppId = "appId";
app.AppSecret = "secret";
var fb = new FacebookWebContext(app);
fb.IsAuthenticated();
App ID and secret are properly set. The stack trace of the exception is the following:
System.Exception occurred
Message=ClientID required. Source=Facebook StackTrace:
at Facebook.FacebookOAuthClient.BuildExchangeCodeForAccessTokenParameters(IDictionary`2 parameters, String& name, String& path)
at Facebook.FacebookOAuthClient.ExchangeCodeForAccessToken(String code, IDictionary`2 parameters)
at Facebook.FacebookSession.get_AccessToken()
at Facebook.FacebookSession.get_Expires()
at Facebook.Web.FacebookWebContext.IsAuthenticated()
at Piedone.FacebookTest.Authorize() InnerException:
On the client side I'm using the JS SDK, initialized as following:
FB.init({
appId: appId,
status: true, // check login status
cookie: true, // enable cookies to allow the server to access the session
xfbml: true, // parse XFBML
oauth: true // enable OAuth 2.0
});
The users gets properly logged in with the JS login() method, as the alert in the following piece of code runs:
FB.login(function (response) {
if (response.authResponse) {
alert("logged in");
} else {
alert('User cancelled login or did not fully authorize.');
}
}, { scope: scope });
In the app settings on Facebook both the "Forces use of login secret for OAuth call and for auth.login" and "Encrypted Access Token" are turned on. As far as I know all this should enable the use of the OAuth 2 authentication.
Anybody has an idea what am I doing wrong? There really can't be any error in these few lines of code...
Thanks in advance for any help!
Edit:
The AccessToken property of FacebookWebContext throws the same error and HttpContext.CurrentNotification does:
CurrentNotification '(_facebookWebContextCache.HttpContext).CurrentNotification' threw an exception of type 'System.PlatformNotSupportedException' System.Web.RequestNotification {System.PlatformNotSupportedException}
This operation requires IIS integrated pipeline mode.
Since I must run the program from Visual Studio with its Development Server (as I'm currently developing the application) there is no way anything can be done about the latter exception, I suppose. Actually I also tried with Webmatrix's IIS express, but the problem persists.
It's also interesting, that in the FacebookWebContext the settings (app id, secret) are correctly set as well, the user Id and the signed request is also there...
Edit 2:
I also get the same error when using the SDK source. It looks that AccessToken and in the Session the Expires property throw the exception. I don't know if this is connected to the httpcontext issue above.
One more solution is add facebook settings to you web or app congfig
<facebookSettings appId="appid" appSecret="secret" />
after that create Auth class
var oauth = new FacebookOAuthClient(FacebookApplication.Current);
And it wil work as well
Finally I managed to solve the problem, but most likely this is a bug in the SDK.
The cause
The problem is that the FacebookApplication.Current is empty, as it does not get populated with data set in the FacebookWebContext ctor. This leads to the problem of the access token: in FacebookSession.AccessToken on line 119 FacebookOAuthClient is instantiated with FacebookApplication.Current, that of course is practically empty. So FacebookOAuthClient is throwing the exception as it doesn't get the application settings.
The solution
The workaround is to simply explicitly set the current FacebookApplication together with the instantiation of FacebookWebContext:
var app = new DefaultFacebookApplication();
app.AppId = "appId";
app.AppSecret = "secret";
var fb = new FacebookWebContext(app);
FacebookApplication.SetApplication(app); // Note this is the new line

Categories