I have a WebApi 2 and a MVC Web project in the same solution running on different IIS ports. After recieving my Oauth token using jQuery AJAX I still get a 401 Unauthorized error message when trying to call an authorized Controller method.
Startup:
public void Configuration(IAppBuilder app)
{
HttpConfiguration httpConfig = new HttpConfiguration();
ConfigureOAuthTokenGeneration(app);
ConfigureOAuthTokenConsumption(app);
ConfigureWebApi(httpConfig);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(httpConfig);
}
CustomOAuthProvider:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<UserManager>();
User user = await userManager.FindAsync(context.UserName, context.Password);
// checks with context.SetError() results.
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager, "JWT");
oAuthIdentity.AddClaim(new Claim(ClaimTypes.Role, "User"));
var ticket = new AuthenticationTicket(oAuthIdentity, null);
context.Validated(ticket);
}
Thinks I've tried from I get "Authorization has been denied for this request." error message when using OWIN oAuth middleware (with separate Auth and Resource Server):
Updating all the Owin packages to latest version (Web project does not use any Owin functionality so it is not installed here).
Api and web are different projects but on same machine, so same machinekey.
OAuth Token configuration comes before the WebApi configuration in Startup.cs.
Claims are made: oAuthIdentity consist out of a role and an admin claim (http://schemas.microsoft.com/ws/2008/06/identity/claims/role: User)
Everything else works as expected (web api, cors, token generation,...), what am I doing wrong? (There is a lot of code involved, so let me know if I need to place an other piece of code from my projects.
EDIT:
Ajax call (Solution by jumuro):
var token = sessionStorage.getItem(tokenKey); // Same as the generated login token
$.ajax({
type: 'POST',
// Don't forget the 'Bearer '!
beforeSend: function (xhr) { xhr.setRequestHeader('Authorization', 'Bearer ' + token) },
url: 'http://localhost:81/api/auth/test', // Authorized method
contentType: 'application/json; charset=utf-8'
}).done(function (data) {
//
});
You have to include an Authorization header with the bearer token in the ajax call. Please see this reponse as an example.
I hope it helps.
Related
I have a .net core 3.1 MVC IdentityServer4 application (A tailored quickstart app provided by the guys at Idsrv) which I use for authentication against a SPA which is built using the oidc-client library and I've recently been able to replicate an issue regarding a 400 error that appears every now and then when trying to login to Identity Server.
Open up two separate tabs on Chrome/Firefox/Edge and navigate to the
path of the client app on both tabs (e.g. https://localhost:5001).
Both tabs correctly redirect to the URL of my identity server
instance as the user has not yet logged in (https://localhost:5002).
Login on the 1st tab with username and password, or even an external
OIDC client - this works fine.
Attempt to login on the 2nd tab with username and password
400 Error occurs and after checking the logs I get the following CORS issue (which I don't really think is related):
[15:51:29 Debug] IdentityServer4.Hosting.CorsPolicyProvider
CORS request made for path: /Account/Login from origin: null but was ignored because path was not for an allowed IdentityServer CORS endpoint
If I click back in the browser the login page shows again, and then I can login as expected.
So to summarise, once a user has successfully logged in to the 1st tab, the 2nd tab will always throw a 400 error the first time a user tries to login to IdentityServer there. I'm really struggling to work out why this could be - and the CORS error message is a little strange too as when I click back and attempt login the 2nd time it works fine.
new Client
{
ClientId = "WebApp",
ClientName = "Web App",
AllowedGrantTypes = GrantTypes.Code,
RequireClientSecret = false,
RequirePkce = true,
AccessTokenType = AccessTokenType.Reference, // This ensures tokens are revoked on logout
RedirectUris = {
$"{webClientHost}/Oidc/SigninOidc",
$"{webClientHost}/Oidc/SilentRefresh"
},
PostLogoutRedirectUris = { webClientHost + "/" },
AllowedCorsOrigins = { webClientOrigin },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerSettings.ApiScopeName
},
AllowAccessTokensViaBrowser = true,
AccessTokenLifetime = (int) TimeSpan.FromMinutes(Convert.ToDouble(isAppSettings.WebAccessTokenLifetime)).TotalSeconds,
RequireConsent = false
}
Has anyone every experienced anything like this? Could it be something to do with my setup at all or is this a known flaw with the Identity Server 4 Quickstart app? I've been trying to debug, but the 400 error I experience means the debugging doesn't even hit so I'm struggling. Thanks
The issue is not related/caused by identity server rather how ASP.Net Core uses antiforgery token. This issue is similar in many cases to an issue reported on github https://github.com/IdentityServer/IdentityServer4/issues/2676.
In this particular instance once the user successfully logs in the application, Identity server issues a session cookie and thus the request token and antiforgery token go out of sync.
Since the preloaded login page relies on the old antiforgery token, once the request starts processing the systems throws following exception -
"Antiforgery token validation failed. "The antiforgery cookie token and request token do not match."
Here is the stack frame -
Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException: The antiforgery cookie token and request token do not match.
at Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgery.ValidateTokens(HttpContext httpContext, AntiforgeryTokenSet antiforgeryTokenSet)
at Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgery.ValidateRequestAsync(HttpContext httpContext)
at Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ValidateAntiforgeryTokenAuthorizationFilter.OnAuthorizationAsync(AuthorizationFilterContext context)
As antiforgery token validation is executed before actions, hence the authorization fails and request processing is aborted, the execution control never reaches the login code.
For those that come here in the future (I am using Duende Identity Server v6), I managed to work around this by adding this middleware which sends them back to the login page with the original request to get a new anti forgery token. If they had logged in correctly the login page will flash then automatically return them to the application otherwise they can just login as normal:
using Microsoft.AspNetCore.Antiforgery;
using System.Net;
namespace IdentityServer
{
public class FixAntiForgeryIssueMiddleware
{
private readonly RequestDelegate next;
private readonly ILogger logger;
private readonly string matchPath;
public FixAntiForgeryIssueMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, string matchPath)
{
this.next = next;
this.matchPath = matchPath;
logger = loggerFactory.CreateLogger<FixAntiForgeryIssueMiddleware>(); ;
}
public async Task Invoke(HttpContext context)
{
await next.Invoke(context);
if (context.Response.StatusCode == (int)HttpStatusCode.BadRequest)
{
if (context.Request.Path.StartsWithSegments(new PathString(matchPath)))
{
if (context.Request.Form.TryGetValue("Input.ReturnUrl", out var returnUrls))
{
var returnUrl = returnUrls.FirstOrDefault();
if (string.IsNullOrEmpty(returnUrl))
{
context.Response.Redirect(matchPath);
}
else
{
context.Response.Redirect(returnUrl);
}
}
}
}
}
}
}
Then in the Progam.cs/HostingExtensions.cs use it like this:
app.UseMiddleware<FixAntiForgeryIssueMiddleware>("/account/login");
I have an ASP.NET Core Web API that uses AzureAd registered app service for authentication.
Its purpose will be to act as a backend service to UI front ends.
I have tested security functionality using postman, headers, JWT Authorization, etc., and that works successfully.
Now, I have created a front end in ASP.NET Core 2.1 as an MVC web app.
I have AzureAd login working on the front end.
As a test, I have tried an Ajax request to the backend Web API.
But, I get a 401 Unauthorized response because I do not know how to pass the Authorization Bearer in the request headers, I presume this is my problem.
My question is how is this done?
How do I authenticate to an ASP.NET Core Web API from an ASP.NET Core Web app?
I would like to use a streamlined approach like this, if possible.
And/Or, is there a better approach?
I found my own answer to this problem after reading about middleware and HttpClient requests.
In case anyone finds their self in the same boat here is how I solved it.
Capture the azure "Bearer" token and store token server-side as an identity claim of the user.
Make a method to send http requests to the Web Api.
Like this as follows:
GETTING AND STORING THE TOKEN
This goes in your startup.cs where you configure servcies for AzureAd/OpenIdConnect.
builder.Services.Configure(configureOptions);
builder.Services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, ConfigureAzureOptions>();
builder.AddOpenIdConnect(o =>
{
//Additional config snipped
o.Events = new OpenIdConnectEvents
{
OnTokenValidated = async context =>
{
ClaimsIdentity identity = context.Principal.Identity as ClaimsIdentity;
if (identity != null)
{
identity.AddClaim(new Claim("access_token", context.SecurityToken.RawData));
}
System.Diagnostics.Debug.WriteLine(context.SecurityToken.RawData + "\n\n");
}
};
});
METHOD FOR SENDING ALL HTTP REQUESTS WITH THE AUTHORIZATION TOKEN.
public async Task<IActionResult> AjaxAction(string url)
{
if (User.Claims == null) return null;
System.Security.Claims.Claim claim = User.Claims.SingleOrDefault(s => s.Type == "access_token");
if (claim == null) return null;
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", claim.Value);
string url_e = System.Web.HttpUtility.UrlEncode(url);
HttpResponseMessage response = await httpClient.GetAsync(url);
// Here we ask the framework to dispose the response object a the end of the user resquest
HttpContext.Response.RegisterForDispose(response);
return new HttpResponseMessageResult(response);
}
I'm struggling to implement a custom auth flow with OAuth and JWT.
Basically it should go as follows:
User clicks in login
User is redirect to 3rd party OAuth login page
User logins into the page
I get the access_token and request for User Info
I get the user info and create my own JWT Token to be sent back and forth
I have been following this great tutorial on how to build an OAuth authentication, the only part that differs is that Jerrie is using Cookies.
What I Have done so far:
Configured the AuthenticationService
services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = "3rdPartyOAuth";
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie() // Added only because of the DefaultSignInScheme
.AddJwtBearer(options =>
{
options.TokenValidationParameters = // Ommited for brevity
})
.AddOAuth("3rdPartyOAuth", options =>
{
options.ClientId = securityConfig.ClientId;
options.ClientSecret = securityConfig.ClientSecret;
options.CallbackPath = new PathString("/auth/oauthCallback");
options.AuthorizationEndpoint = securityConfig.AuthorizationEndpoint;
options.TokenEndpoint = securityConfig.TokenEndpoint;
options.UserInformationEndpoint = securityConfig.UserInfoEndpoint;
// Only this for testing for now
options.ClaimActions.MapJsonKey("sub", "sub");
options.Events = new OAuthEvents
{
OnCreatingTicket = async context =>
{
// Request for user information
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var user = JObject.Parse(await response.Content.ReadAsStringAsync());
context.RunClaimActions(user);
}
};
});
Auth Controller
[AllowAnonymous]
[HttpGet("login")]
public IActionResult LoginIam(string returnUrl = "/auth/loginCallback")
{
return Challenge(new AuthenticationProperties() {RedirectUri = returnUrl});
}
[AllowAnonymous]
[DisableRequestSizeLimit]
[HttpGet("loginCallback")]
public IActionResult IamCallback()
{
// Here is where I expect to get the user info, create my JWT and send it back to the client
return Ok();
}
Disclaimer: This OAuth flow is being incorporated now. I have a flow for creating and using my own JWT working and everything. I will not post here because my problem is before that.
What I want
In Jerrie's post you can see that he sets DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;. With that, when the /auth/loginCallback is reached I have the user claims in the HttpContext.
The problem is my DefaultAuthenticateScheme is set to JwtBearersDefault and when the loginCallback is called I can't see the user claims nowhere in the Request.
How can I have access to the information gained on the OnCreatingTicketEvent in my callback in this scenario?
Bonus question: I don't know much about OAuth (sure that is clear now). You may have noted that my options.CallbackPath differs from the RedirectUri passed in the Challenge at the login endpoint. I expected the option.CallbackPath to be called by the 3rd Part OAuth provider but this is not what happens (apparently). I did have to set the CallbackPath to the same value I have set in the OAuth provider configuration (like Jerries tutorial with GitHub) for it to work. Is that right? The Callback is used for nothing but a match configuration? I can even comment the endpoint CallbackPath points to and it keep working the same way...
Thanks!
Auth
As Jerrie linked in his post, there is a great explanation about auth middlewares:
https://digitalmccullough.com/posts/aspnetcore-auth-system-demystified.html
You can see a flowchart in the section Authentication and Authorization Flow
The second step is Authentication middleware calls Default Handler's Authenticate.
As your default auth handler is Jwt, the context is not pupulated with the user data after the oauth flow,
since it uses the CookieAuthenticationDefaults.AuthenticationScheme
Try:
[AllowAnonymous]
[DisableRequestSizeLimit]
[HttpGet("loginCallback")]
public IActionResult IamCallback()
{
//
// Read external identity from the temporary cookie
//
var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
if (result?.Succeeded != true)
{
throw new Exception("Nein");
}
var oauthUser = result.Principal;
...
return Ok();
}
Great schemes summary: ASP.NET Core 2 AuthenticationSchemes
You can persist your user with
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.authenticationhttpcontextextensions.signinasync?view=aspnetcore-2.2
Bonus
I did have to set the CallbackPath to the same value I have set in the OAuth provider configuration (like Jerries tutorial with GitHub) for it to work. Is that right?"
Yes.
For security reasons, the registered callback uri (on authorization server) and the provided callback uri (sent by the client) MUST match.
So you cannot change it randomly, or if you change it, you have to change it on the auth server too.
If this restriction was not present, f.e. an email with a mailformed link (with modified callback url) could obtain grant.
This is called Open Redirect, the rfc refers to it too: https://www.rfc-editor.org/rfc/rfc6749#section-10.15
OWASP has a great description: https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md
I can even comment the endpoint CallbackPath points to and it keep working the same way..."
That is because your client is trusted (you provide your secret, and you are not a fully-frontend Single Page App). So it is optional for you to send the callback uri.
But IF you send it, it MUST match with the one registered on the server. If you don't send it, the auth server will redirect to the url, that is registered on its side.
https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1
redirect_uri
OPTIONAL. As described in Section 3.1.2.
https://www.rfc-editor.org/rfc/rfc6749#section-3.1.2
The authorization server redirects the user-agent to the
client's redirection endpoint previously established with the
authorization server during the client registration process or when
making the authorization request.
https://www.rfc-editor.org/rfc/rfc6749#section-3.1.2.2
The authorization server MUST require the following clients to register their redirection endpoint:
Public clients.
Confidential clients utilizing the implicit grant type.
Your client is confidential and uses authorization code grant type (https://www.rfc-editor.org/rfc/rfc6749#section-1.3.1)
https://www.rfc-editor.org/rfc/rfc6749#section-3.1.2.3
If multiple redirection URIs have been registered, if only part of
the redirection URI has been registered, or if no redirection URI has
been registered, the client MUST include a redirection URI with the
authorization request using the "redirect_uri" request parameter.
You have registered your redirect uri, that's why the auth server does not raise an error.
change [AllowAnonymous]
to [Authorize]
on the 'loginCallback' endpoint (AuthController.IamCallback method)
I am using Asp.net Identity for token based authentication in my web api application. The problem is I have to perform some operations after the token is generated and the user is authenticated and before the redirection to client side occurs.
I have a login page which uses /token token authentication . Once the token is issued i need to keep the user and token values in a session. [this session will be used to show online users.]
Client request
$('#btnLogin').click(function () {
$.ajax({
// Post username, password & the grant type to /token
url: '/token',
method: 'POST',
contentType: 'application/json',
data: {
username: $('#txtUsername').val(),
password: $('#txtPassword').val(),
grant_type: 'password'
}
});
Server side
[HttpPost]
public void Login()
{
OnlineUsers user = new OnlineUsers();
var users = (HttpContext.Current.Session["ActiveUsers"] as
List<OnlineUsers>) ?? new List<OnlineUsers>();
users.Add(user);
HttpContext.Current.Session["ActiveUsers"] = users;
}
I need to call this controller method after the token is issued and use is authenticated.
Is there any solution to this?
If you want to intercept generation of the token think you have to customize the aspnet identity behaviour
https://msdn.microsoft.com/en-us/library/microsoft.aspnet.identity.usermanagerextensions.generateusertoken(v=vs.108).aspx
With Web API using session is not a good approach, to keep user's information in client side you can use browser's localstorage. Once the user authenticated via your login api controller, you can return the user's required info as json to client, then you can keep it to browsers. Web API is by default stateless so i think session is not suitable with it, additional burden on the client. Storing session state on the server violates the stateless constraint of the REST architecture. So the session state must be handled entirely by the client.
Web API
[HttpPost]
public IHttpActionResultLogin()
{
OnlineUsers user = new OnlineUsers();
user=YourUserDetailsMethod();
return Ok(user);
}
Client:
$('#btnLogin').click(function () {
$.ajax({
// Post username, password & the grant type to /token
url: '/token',
method: 'POST',
contentType: 'application/json',
data: {
username: $('#txtUsername').val(),
password: $('#txtPassword').val(),
grant_type: 'password'
},
success: function(response){
window.localStorage.setItem('userInfo', response);
$('#UserName').val(window.localStorage.getItem('userInfo').UserName);
}
});
I'm trying to integration test my web api controllers. The application uses JWTs to authenticate users against the resource server.
To spool up the application, I'm using the TestServer found in Microsoft.OWIN.Testing.
I can obtain a valid JWT by performing a login as a browser would do. I then proceed to add the JWT to the request as follows:
request.AddHeader("Authorization", "Bearer " + accessToken.RawData);
That header also arrives in the OWIN pipeline. However, all controllers protected with the [Authorize]-attribute return 401 Unauthorized when invoked.
The API is protected using IdentityServer3 by Thinktecture, the relevant section looks like this:
var authority = "http://localhost:8080/idsrv/";
var parameters = new TokenValidationParameters() { ValidAudiences = new[] { "implicitclient" } };
var options = new IdentityServerBearerTokenAuthenticationOptions
{
Authority = authority,
TokenValidationParameters = parameters
};
app.UseIdentityServerBearerTokenAuthentication(options);
var configuration = new WebApiConfiguration(this.container);
configuration.Configuration(app);
I don't really know where to look for any pointers to the problem, so any help is appreciated.
Do you want to really test with the token middleware? I mean - you are not testing the token middleware itself - but the controller logic based on certain authentication outcomes.
Just write a small inline middleware that sets Context.Authentication.User to some ClaimsPrincipal you want to test with.
app.Use(async (ctx, next) => { ctx.Authentication.User = somePrincipal; await next() };