Why is context.Request.Context.Authentication.SignIn not generating a cookie? - asp.net

We have an MVC 5 application that we have added Web Api Controllers to in order to provide REST API functionality. We have successfully implemented OAuth authentication through the OWIN pipeline using a custom OAuth Provider class.
Now we want to implement authentication cookies as well to protect static resources on the server. I'm sure there's a million other ways to do this, but the request for the resource is a link directly to that resource so I can't use my OAuth token or any other mechanism which is why we want to use cookies...the browser sends them already, no need to change anything.
From everything I've read it is possible to do both Bearer Token authentication and Cookie authentication with the OWIN Pipeline. Basically Web API will use Bearer Tokens cause that's all the client will supply and requests for certain static resources on the server will use Cookies which are sent on all requests.
Our problem is that with the code below an auth cookie is never generated. Throughout the pipeline I never see a set-cookie header on the response, which is why I added the Kentor Cookie Saver to the pipeline...it's supposed to help.
WebApiConfig.cs
...
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
...
Startup.Auth.cs
...
app.UseOAuthBearerTokens(OAuthOptions);
// I was told this might help with my cookie problem...something to do with System.Web stripping Set-Cookie headers
app.UseKentorOwinCookieSaver();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = Microsoft.Owin.Security.Cookies.CookieAuthenticationDefaults.AuthenticationType,
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
ExpireTimeSpan = TimeSpan.FromHours(4)
});
...
Custom OAuth Provider
...
// Creates our claims and properties...keep in mind that token based authentication is working
CreatePropertiesAndClaims(acct, out properties, out claims);
if (IsAccountAuthorized(claims))
{
AuthenticationProperties authProps = new AuthenticationProperties(properties);
ClaimsIdentity claimsIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
claimsIdentity.AddClaims(claims);
AuthenticationTicket ticket = new AuthenticationTicket(claimsIdentity, authProps);
context.Validated(ticket);
ClaimsIdentity cookieIdentity = new ClaimsIdentity(claims, Microsoft.Owin.Security.Cookies.CookieAuthenticationDefaults.AuthenticationType);
context.Request.Context.Authentication.SignIn(cookieIdentity); // This should create the auth cookie!??!
}
else
{
context.SetError("Unauthorized", "You don't currently have authorization. Please contact support.");
}
...
Keep in mind that Token based authentication is working so I assume it's a configuration setting missing or misconfigured, or a pipeline ordering issue.
THANK YOU!

I know it is a late answer, but we came across exactly the same problem. My colleague and I spent 4 hours trying to figure out why. Here is the answer that hopefully can save somebody else from bang their head against the wall.
Inspect the response, you can see there is Set-Cookie:.AspNet.Cookies=LqP1uH-3UZE-ySj4aUAyGa8gt .... But the cookies it is not saved. What you need to do is, in your ajax call, include credentials.
$.ajax({
url: url,
type: 'POST',
xhrFields: {
withCredentials: true
}
)
we are using the Fetch API, it looks like the following
fetch(url, {
method: 'post',
headers: {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
},
credentials: 'include'
})

Related

Protect ASP.NET API with IdentityServer & Bearer

I have a 3 tier application, where I have:
A blazor web application
a HTTP API
An Identity Server
Currently, I have 2 kinds of controllers on the HTTP API:
general-purpose API: can be accessed without authentication, but only if the call comes from the Web Application
personal purpose API: can be accessed only after authentication
Right now, the "personal purpose API" working fine. this API is only accessible when the user is logged in.
But, I also need to protect the "general-purpose API" from any hacker, right now a call to "post/list" returns an "unauthorized error"!
I wish to protect this API, without authentication, but it must be only accessible from my web application.
Do you know how can I do this? Is there something wrong or missing in my code?
Here is the controller code on the HTTP API side :
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class PostsController : ControllerBase
{
[HttpGet]
[Route("post/list")]
public async Task<List<Item>> GetListAsync()
{
...
}
}
Here is my code on the HTTP API side :
context.Services.AddAuthentication(Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = true;
options.ApiName = "SoCloze";
});
And here is my code on the Web Application side:
context.Services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies", options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(ApplicationConstants.LoginCookieExpirationDelay);
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = true;
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.ClientId = configuration["AuthServer:ClientId"];
options.ClientSecret = configuration["AuthServer:ClientSecret"];
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("role");
options.Scope.Add("email");
options.Scope.Add("phone");
options.Scope.Add("SoCloze");
options.ClaimActions.MapAbpClaimTypes();
});
You should make your controller anonymous. With your configuration simply remove your Authorize attribute from your controller. If you had applied a global policy you'd need to add the anonymous attribute to your controller:
[AllowAnonymous]
I'm not sure what you want to achieve, but I guess you want cross-site protection. That is, you want to limit certain endpoints from being accessed only from the origin of your blazor app.
You need to trust in the client browser for Same Origin Policy. The same origin policy controls interactions between two different origins. Cross-origin writes are typically allowed, Cross-origin embedding is typically allowed and Cross-origin reads are typically disallowed, but read access is often leaked by embedding.
This protection is only for XHR calls from your browser and it doesn't protect againts direct access (postman, fiddler, any http client...).
To prevent cross-origin writes in your posts you can use Antiforgery tokens. Asp Net Core create this tokens automatically if you specify POST in your forms:
<form asp-controller="Todo" asp-action="Create" method="post">
...
</form>
Or
#using (Html.BeginForm("Create", "Todo"))
{
...
}
This will reject any post from any form that has not been previously loaded from your server.
To keep the API simple, you of course want to only accept tokens from the trusted IdentityServer.
So basically you need a generic access token that is secure, but not tied to any human user. You could have the web app to request an access token using the client credentials flow and then use that token all the time when you want to access the API as a non-authenticated user.
In IdentityServer you would setup a separate client just for this.
Also using the IdentityModel library could help you out here (as a Worker Applications) https://identitymodel.readthedocs.io/en/latest/aspnetcore/worker.html
see also https://docs.duendesoftware.com/identityserver/v5/tokens/requesting/
This picture shows what I mean:

Token based authentication http interceptor

I would like your opinion in order to achieve something. I have an api and I want to restrictionate the access of the user. But short story long, in action login controller of page /login I generate the token for the user autheticated and I return it. Well, here comes one of my question/problem. It's mandatory in a action controller to return a response, and my token it's send in json format and it's displayed on browser, but I don't want this, I just want to keep in response or in something so that my angular part to take that token and to take care of it in its way. So my method login:
public function loginAction(Request $request)
{
$username= $this->get('security.token_storage')->getToken()->getUser()->getEmail();
$serviceUser = $this->container->get('ubb_user_login');
$tokenUser = $serviceUser->generateToken($username);
$response = new JsonResponse(array('Token' => $tokenUser));
return $response;
}
2nd problem. I do not know in angular where to extract the token. In a controller I tried something:
app.controller('loginApi', function ($scope, $http, $window){
$http.get(
'/api/user/login'
).then(function (success) {
$scope.token = success.data.Token;
$window.localStorage.setItem('Token', token); // it's okay used like this localStorage?
});
});
Here, I want only to take the token from /login action, to save it in local storage, and then to display it on every request using $http request interceptor. That part works. If I send a random string in header auth it's working and it gives me access to api:
function httpInterceptor() {
return {
request: function(config) {
config.headers.authorization = 'jdjdnnkdkd';
return config;
},
responseError: function(errorResponse) {
switch (errorResponse.status) {
case 403:
window.location = '/login';
break;
case 401:
window.location = '/login';
}
return $q.reject(errorResponse);
}
}
}
So, the problems I encounter:
1) How to send the token in order for angular to take it, but to not be displayed in the browser? Let's say that I have a menu that access the api, and if the user it's not authenticated,clicking on a link unauthenticated it's sending me on the login page, and after I auth with success, to redirect me on the menu?
2) After returning the token, where exactly to use it in angular? In which controller? And how to save it?
Much appreciation, thank you.
It seems like you need a separate api resource for working with the tokens. Have you tried using FOSOAuthServeBundle? It helps a lot with setting up oauth authentication, tokens etc.
In general you need to have a separate call for the token, i.e.:
angluar app makes request to a get token resource, a token is returned and temporarily stored in the angular app
use that token for each subsequent request - check the oauth bundle config on how to set which urls have to be oauth protected, but that bundle takes care of this for you
Regarding your angular issue, look at this q/a How to store authentication bearer token in browser cookie using AngularJS

How do I issue the corresponding Bearer and Cookie identity in ASP.NET with multiple Authorization schemes?

This documentation describes in part how to use more than one authentication scheme:
In some scenarios, such as Single Page Applications it is possible to end up with multiple authentication methods. For example, your application may use cookie-based authentication to log in and bearer authentication for JavaScript requests. In some cases you may have multiple instances of an authentication middleware. For example, two cookie middlewares where one contains a basic identity and one is created when a multi-factor authentication has triggered because the user requested an operation that requires extra security.
Example:
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationScheme = "Cookie",
LoginPath = new PathString("/Account/Unauthorized/"),
AccessDeniedPath = new PathString("/Account/Forbidden/"),
AutomaticAuthenticate = false
});
app.UseBearerAuthentication(options =>
{
options.AuthenticationScheme = "Bearer";
options.AutomaticAuthenticate = false;
});
However it only describes how to use Bearer or Cookie auth. What isn't clear is what other combinations are valid, or how to properly issue bearer or cookies to the client.
How can that be accomplished?
One common use case for this which large sites like Facebook, Google etc. use is to use multiple cookie authentication middleware's and set one of them to be the default using AutomaticAuthenticate
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationScheme = "InsecureLongLived",
LoginPath = new PathString("/Account/Unauthorized/"),
AccessDeniedPath = new PathString("/Account/Forbidden/"),
AutomaticAuthenticate = true
});
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationScheme = "SecureAndShortLived",
LoginPath = new PathString("/Account/Unauthorized/"),
AccessDeniedPath = new PathString("/Account/Forbidden/"),
AutomaticAuthenticate = false
});
The default one is long lived and used for non-critical auth scenarios e.g. on Facebook, this may be to view your profile page.
The more secure and short lived on is used for security critical user actions like changing your password or profile information.
This gives you the convenience of not having to login all the time with a long lived cookie but as soon as you need to do something potentially dangerous, you switch to doing auth with a much shorter lived and thus more secure cookie which requires the user to login again.

Extend forms authentication to use a custom http header for ticket

I have a wcf webhttp service which uses forms authentication to authenticate users. This works fine if the ticket comes in the cookie collection or in the url.
But now I want to send the string of the forms auth ticket in a custom http header and change the forms auth module to check for that header instead of the cookie.
I think it should be easy to extend forms auth to achive this, but could not find any resources of how to. Can you point me in the right direction ?
here's how my authentication flow would work,
A client calls the authenticate method with the username and pwd
Service returns the encrypted ticket string
Client send the received ticket string in a http header with every subsequent request
Service checks for auth header and validates the auth ticket
FormAuthentication module is not extendible, but you could write your own authentication.
It is very simple:
Authentication(2):
var formsTicket = new FormsAuthenticationTicket(
1, login, DateTime.Now, DateTime.Now.AddYears(1), persistent, String.Empty);
var encryptedFormsTicket = FormsAuthentication.Encrypt(formsTicket);
//return encryptedFormsTicket string to client
Service call with attached ticket(4):
var ticket = FormsAuthentication.Decrypt(encryptedFormsTicket)
//extract authentication info from ticket: ticket.Name
I am not sure this is the way to go (elegance-wise), but what about adding an event in global.asax.cs for Application BeginRequest and taking the string from the header and injecting a cookie into the Request yourself (Forms authentication should then pick that up).
Something like:
protected void Application_BeginRequest()
{
// Your code here to read request header into cookieText variable
string cookieText = ReadCookieFromHeader();
var cookieData = FormsAuthentication.Decrypt(cookieText);
if (!cookieData.Expired)
{
HttpContext.Current.Request.Cookies.Add(new HttpCookie(cookieData.Name, cookieText));
}
}
DISCLAIMER: Please note that I didn't test this, just throwing a possible approach your way!

DotNetOpenAuth Failing to work on Live Server

I worked on a sample application integrating OpenID into ASP.NET Web Forms. It works fine when hosted locally on my machine. However, when I uploaded the application to a live server, it started giving "Login Failed".
You can try a sample here: http://samples.bhaidar.net/openidsso
Any ideas?
Here is the source code that fails to process the OpenID response:
private void HandleOpenIdProviderResponse()
{
// Define a new instance of OpenIdRelyingParty class
using (var openid = new OpenIdRelyingParty())
{
// Get authentication response from OpenId Provider Create IAuthenticationResponse instance to be used
// to retreive the response from OP
var response = openid.GetResponse();
// No authentication request was sent
if (response == null) return;
switch (response.Status)
{
// If user was authenticated
case AuthenticationStatus.Authenticated:
// This is where you would look for any OpenID extension responses included
// in the authentication assertion.
var fetchResponse = response.GetExtension<FetchResponse>();
// Store the "Queried Fields"
Session["FetchResponse"] = fetchResponse;
// Use FormsAuthentication to tell ASP.NET that the user is now logged in,
// with the OpenID Claimed Identifier as their username.
FormsAuthentication.RedirectFromLoginPage(response.ClaimedIdentifier, false);
break;
// User has cancelled the OpenID Dance
case AuthenticationStatus.Canceled:
this.loginCanceledLabel.Visible = true;
break;
// Authentication failed
case AuthenticationStatus.Failed:
this.loginFailedLabel.Visible = true;
break;
}
}
As Andrew suggested, check the exception. In my case, my production server's time & date were off and it wouldn't authenticate because the ticket expired.
Turn on logging on your live server and inspect them for additional diagnostics. It's most likely a firewall or permissions problem on your server that prevents outbound HTTP requests.
You may also find it useful to look at the IAuthenticationResponse.Exception property when an authentication fails for clues.

Resources