OpenIdConnect HTTP Basic authentication scheme for client authentication - asp.net-mvc

I'm trying to implement an OpenIdConnect login to my .net core 2.0 site.
The IdentityServer I'm trying to use only supports 'client_secret_basic' as token_endpoint_auth_methods.
I configured the application as follows:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "Cookies";
options.DefaultSignInScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.SignInScheme = "Cookies";
options.Authority = Auhtority;
options.ClientId = ClientID;
options.ClientSecret = ClientSecret;
options.ResponseType = "code";
options.SaveTokens = true;
options.Scope.Add("profile");
options.Scope.Add("rrn");
});
But this seems to post the ClientId and ClientSecret to the body of the request and not using HTTP Basic authentication.
I'm probably missing something obvious, but I can't seem to find how to configure the OpenIdConnect to use client_secret_basic instead of client_secret_post.
Any ideas?

I had the same issue and I finally got it to work using the following options / event hook using System.Net.Http;:
options.Events.OnAuthorizationCodeReceived = context =>
{
context.Backchannel.SetBasicAuthenticationOAuth(context.TokenEndpointRequest.ClientId, context.TokenEndpointRequest.ClientSecret);
return Task.CompletedTask;
};
I got this mostly out of the comments to your question and with the method mentioned there being marked as deprecated with a hint to the new extension method I use.

In 2022 you still can use the IdentityModel nuget.
It has some System.Net.Http extension methods.
https://github.com/IdentityModel/IdentityModel/blob/6f9e050167846724828138ba6ee8b626eb31c669/src/Client/BasicAuthenticationOAuthHeaderValue.cs
Keep in mind you have to remove the client_id/client_secret from the TokenEndpointRequest object.

Related

AddOpenIdConnect options.Authority overriden with current host behind proxy (UseForwardedHeaders)

I host my application behind reverse proxy inside container. I'm using IdentityServer4 and I'm trying to make oidc SSO with Azure AD. It works when the application is not using app.UseForwardedHeaders(opts), but I need them.
.NET Core version: 3.1
Forwarded headers configurations:
var opts = new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto |
ForwardedHeaders.XForwardedHost
};
opts.KnownProxies.Add(ipAddress);
app.UseForwardedHeaders(opts);
OpenID Connect Configurations:
services.AddAuthentication( options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
}
)
.AddOpenIdConnect("aadrm", "Azure AD", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.Authority = "https://login.microsoftonline.com/{tenantID...}/v2.0";
options.ClientId = ".....";
options.ClientSecret = #"........";
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
});
My Controller Action:
[HttpGet]
public IActionResult ExternalLoginChallenge(string provider, string returnUrl)
{
var callbackUrl = Url.Action(nameof(ExternalLoginCallback));
var props = new AuthenticationProperties
{
RedirectUri = callbackUrl,
Items =
{
{ "scheme", provider },
{ "returnUrl", returnUrl }
}
};
return Challenge(props, provider);
}
When I initiate the sign in process it should redirect me to the Microsoft's sign in page, but instead it redirects me back to my host with this url: https://mylocalapp.com:443/{tenantId}/oauth2/v2.0/authorize?client_id=...&redirect_uri=HTTPS%3A%2F%2Fmylocalapp.com%2Fsignin-oidc&response_type=code%20id_token&scope=openid%20profile&response_mode=form_post&nonce=........&state=............&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=6.8.0.0
When I remove app.UseForwardedHeaders(opts) it works normally (redirects me to MS login page). It seems that UseForwardedHeaders overrides the OpenIdConnect Authority Address host.
Can you help me, I don't understand why it redirects me back to my host?
Thanks.
I've fount the issue and here is the solution, just in case someone is using IIS as reverse proxy in their local setup.
The issue was caused by the IIS reverse proxy configurations, that I'm using locally - Application Request Routing -> Server Proxy Settings -> Reverse rewrite host in response headers.

Creating and validating JWT token in asp net core MVC application (which does not have client server approach)

I have seen many examples to generate and validate JWT token in WEP API.
WEP API will have client and server approach. Hence we will validate user and generate JWT token in server and send to client. Client will store the token in browser memory next time through httpclient token will attached in request header and send it again to server. Now server will validate those token before hitting controller and allow to access those resource.
But in MVC application we don't have client and server approach. it will send view pages as result to browser.
My question is in MVC controller I have validated the user and created JWT token,
Now how to store the token in client
How to attach the token in request header.
Where should I do the token validation logic in MVC.
Where should I do the refresh token logic in MVC.
Thanks in Advance
According to your question, here are several solutions.
Store token in cookie, this is the recommended practice. Example:
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie(config=>
{
config.Cookie.Name = "auth";
})
.AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
//...
};
});
Although the backend can serialize the token and send it to the view (using JavaScript to store the token in Localstorage), this is not safe. Cookie can avoid csrf attacks. It is suitable for single page application.
If you put it in LocalStorage or SessionStorage, you need to get the token first and put it in the header of the request (take ajax as an exmple). Otherwise, no other configuration is required.
beforeSend: function(request) {
request.setRequestHeader("Authorization", sessionStorage.getItem("Authorization"));
}
You need to add [Authorize] to some actions, it will trigger this authentication which you configed in service. Or you can add a action to parse the tocken to get the context.
When someone update own information, you can regenerate a tocken and then send it to view to update the LocalStorage or cookie. It will carry this token in the next request.
The view can send a request to authenticate.
public IActionResult Authenticate()
{
//...
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
Response.Cookies.Append("authname", tokenString);
return View("index");
}
In startup (ConfigureServices), you can config the getting method with cookie.
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie(config=>
{
config.Cookie.Name = "authname";
})
.AddJwtBearer(o =>
{
o.Events = new JwtBearerEvents()
{
//get cookie value
OnMessageReceived = context =>
{
var a = "";
context.Request.Cookies.TryGetValue("authname", out a);
context.Token = a;
return Task.CompletedTask;
}
};
o.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role,
ValidIssuer = "http://localhost:5200",
ValidAudience = "api",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("this is a long key------------------------"))
//...
};
});

Identity server 4 : how to add Instagram login workflow

i have read a lot of documentation and i have already implemented IS4 with Twitter, Google, Facebook login/registration workflow successfully.
But i have a lot of problem to implements Instagram.
I have add at startup:
.AddOpenIdConnect("oidc", "Instagram", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.SaveTokens = true;
options.ClientId = "xxxxxxxxxxxxxxxxxxxxx";
options.ClientSecret = "yyyyyyyyyyyyyyyyyyyyyyyyy";
options.Authority = "https://api.instagram.com/oauth/authorize/";
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
});
I have implements "InstagramAuthProvider" equal than google one in the Example Project of IS4.
From now i can't start the login flow because i have that error:
An unhandled exception occurred while processing the request. HttpRequestException: Response status code does not indicate success:
404 (Not Found).
System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
IOException: IDX20804: Unable to retrieve document from: '[PII is
hidden]'.
Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(string
address, CancellationToken cancel)
InvalidOperationException: IDX20803: Unable to obtain configuration
from: '[PII is hidden]'.
Microsoft.IdentityModel.Protocols.ConfigurationManager.GetConfigurationAsync(CancellationToken
cancel)
I really not find something useful about that, possible no one implements Instagram in IS4?
Any suggestion? I'm completely stucked.
You can use Instagram authentication provider for ASP.NET Core here:
Install AspNet.Security.OAuth.Instagram nuget package
Add code:
services.AddAuthentication()
.AddInstagram(
options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = "client-Id";
options.ClientSecret = "client-secret";
});
Read Instagram instructions to set the params
Read more here

How to get token well retrieved in services into controller (Core 2.0 oAuth 2)?

In ASP.Net MVC Core 2, we are trying to call the Linkedin web API with OAuth authentication.
We are able to declare the OAuth authentication service and retrieve the access token from Linkedin as shown in the code below.
Now we would like to request the API from a controller. To do that, we have to get the access token from the OAuth service we have declared with the AddOAuth method. How can we do that? No way to find an example anywhere.
Thanx for your help, we are really stuck.
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie("linkedin_login", options =>
{
options.LoginPath = new PathString("/login");
options.LogoutPath = new PathString("/logout");
})
.AddOAuth("LinkedIn", options =>
{
options.SignInScheme = "linkedin_login";
options.ClientId = Configuration["linkedin:clientId"];
options.ClientSecret = Configuration["linkedin:clientSecret"];
options.CallbackPath = new PathString("/signin-linkedin");
options.AuthorizationEndpoint = "https://www.linkedin.com/oauth/v2/authorization";
options.TokenEndpoint = "https://www.linkedin.com/oauth/v2/accessToken";
options.UserInformationEndpoint = "https://api.linkedin.com/v1/people/~:(id,first-name,last-name,formatted-name,email-address,picture-url,picture-urls,headline,public-profile-url,industry,three-current-positions,three-past-positions,positions::(original))";
// To save the tokens to the Authentication Properties we need to set this to true
// See code in OnTicketReceived event below to extract the tokens and save them as Claims
options.SaveTokens = true;
options.Scope.Add("r_basicprofile");
options.Scope.Add("r_emailaddress");
options.Events = new OAuthEvents
{
OnCreatingTicket = async context =>
{
#region OnCreatingTicket
// Retrieve user info
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
//We have here the token: context.AccessToken
request.Headers.Add("x-li-format", "json"); // Tell LinkedIn we want the result in JSON, otherwise it will return XML
the solution is just :
var AccessToken = await HttpContext.GetTokenAsync("LinkedIn", "access_token");
with "LinkedIn" the scheme of the desired oAuth

WebApi2 Google OAuth2 middleware error response

For user authentication with external providers such as Google, it is using specific Owin middlewares. As for example Microsoft.Owin.Security.Google. WebAPI2 template uses this to support implicit flow authentication (response_type=token). But what about Code flow?
Is it possible to implement Code flow (response_type=code)?
After debugging those OAuth providers I noticed that passing return_type=code to Google, it successfully authenticates and returns json with access and refresh tokens, then user gets signed in by api/Account/ExternalLogin endpoint but at the end of the flow I get redirected to
http://localhost:50321/?error=unsupported_response_type#.
I could not really find the flow where and why it is setting this specific error in the assembly.
Startup.Auth.cs looks like this:
public void ConfigureAuth(IAppBuilder app)
{
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
PublicClientId = "self";
var tokenTimeSpanInHours = ConfigurationManager.AppSettings["AccessTokenLifeTimeInHours"];
OAuthServerOptions = new OAuthAuthorizationServerOptions
{
Provider = new ApplicationOAuthProvider(PublicClientId),
TokenEndpointPath = new PathString("/api/token"),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(Convert.ToInt16(tokenTimeSpanInHours)),
AllowInsecureHttp = true
};
app.UseOAuthBearerTokens(OAuthServerOptions);
var googleOAuthOptions = new GoogleOAuth2AuthenticationOptions
{
AccessType = "offline",
Provider = new CustomGoogleAuthProvider(),
ClientId = ConfigurationManager.AppSettings["GoogleAccountClientId"].ToString(),
ClientSecret = ConfigurationManager.AppSettings["GoogleAccountClientSecret"].ToString()
};
googleOAuthOptions.Scope.Add("profile");
googleOAuthOptions.Scope.Add("email");
googleOAuthOptions.Scope.Add("https://www.googleapis.com/auth/gmail.send");
app.UseGoogleAuthentication(googleOAuthOptions);
}
Where is the problem then? Do I need some explicit configuration to tell that I want code flow? Is it supported?

Resources