How to integrate nancy with asp.net Form based authentication - c#

We are in process to migrate our asp.net based application to nancy, as Application is quite big, so we have decided to make application hybrid & migrate it to nancy page by page.
Now we have a nancy login screen, using which we need to do asp.net form authentication as well. Can someone guide us how can we authenticate user with asp.net form based authentication in nancy code.
We are planning to use stateless authentication on nancy side.
Any pointer is highly appreciated.
We are having a nancy login page to validated user credentials
1. Login Module -
Post["/login"] = p =>
{
// Verify user crdentials first
// Success, create non-persistent authentication cookie.
System.Web.Security.FormsAuthentication.SetAuthCookie(credentials.tbUser, false);
var cookie = System.Web.Security.FormsAuthentication.GetAuthCookie(credentials.tbUser, false);
cookie.Expires = DateTime.UtcNow.AddHours(5);
HttpContext.Current.Response.Cookies.Set(cookie);
return Response.AsRedirect("~/listRequest.aspx"); // used Nancy's redirect model to navigate to another page
}
2. Global.asax -
void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
FormsIdentity fIdent = application.Context.User.Identity as FormsIdentity;
if (fIdent != null) **// It always returns null even after ".ASPXAUTH" cookie is successfully created**
{
FormsAuthenticationTicket ticket = fIdent.Ticket; // to see if the ticket exists
}
}

One of the ideas I have not tested but seems likely to be is to use the LoginWithoutRedirect method of Nancy.Authentication.Forms.
Your application will manage authentication cookies for ASP.NET and authentication cookies for Nancy Forms at the same time.

Related

Authentication/Authorization in ASP.NET Core using Enterprise SingleSignon page and Microsoft SignInManager

Presently I am working on an authentication issue in one of my ASP.NET Core(3.0) application.
To give some background, we have to use an Enterprise Single sign on page to authenticate the users. Once the user got authenticated it redirects back to our application along with user name in a HTTP header called "SM_USER". Then using that information we load corresponding Claim information from DB using Microsoft SignInManger. The application is working fine if the user is accessing the root but it couldn't able to access if they are trying to navigate a specific page directly, like http://website/Controller/Index.
I am suspecting that we may have implemented it wrongly so would like to know how should the below scenario to be implemented?
Our users first get authenticated using an enterprise Login Page(Single sign on) then redirected to our application. user information available on HTTP Headers and the corresponding claims information is available in our DB which we need to use for authorization purpose(we wanted to use Microrosoft SignInManger to load them).
I found the issue after researching it throughly so sharing here in case it useful for someone.
we observed whenever a user try to access a page if the user is not authenticated then it is redirecting Login page(Observed that decorating them with [Authorize] attribute is causing this), where We are using this Login page for local development purpose.
so when the user get redirected to login page and if the environment is not development then below code is executed on the Get method, which takes care of signing the user and creating UserPrincipal. Then after that we redirecting to the page the user requested.
if (!_signInManager.IsSignedIn(User))
{
string userName = HttpContext.Request.Headers["SM_USER"].ToString();
if (userName.Length > 0)
{
var user = await _userManager.FindByNameAsync(userName);
if (user != null)
{
var claimsPrincipal = await _signInManager.CreateUserPrincipalAsync(user);
await _signInManager.Context.SignInAsync(IdentityConstants.ApplicationScheme,
claimsPrincipal,
new AuthenticationProperties { IsPersistent = true });
if (string.IsNullOrEmpty(returnUrl)) //returnUrl is a parameter get passed by the system.
{
return RedirectToAction("<Action>", "<Controller>");
}
else
{
return Redirect(returnUrl);
}
}
}
}

SSRS Forms Authentication - How To Pass Cookie Credentials To Report Server

I am currently attempting to render the SSRS report in my web application using forms authentication.
My SSRS Report Version is 2016.
Initially I was under the impression that NetworkCredentials would work, and after encountering errors, I found that we are required to use FormsAuthentication, with passing the cookie as a means of authenticating the user.
I have done the necessary settings on the config files in the Reporting Server by following the guide from the link below:-
https://github.com/Microsoft/Reporting-Services/tree/master/CustomSecuritySample2016
The reporting services works as intended on the localhost/ReportServer and on
the SSRS Portal, localhost/Reports. I am also able to access said server
remotely.
Below is the code I used to obtain the authenticated cookie.
MyReportingService rsClient = new MyReportingService();
rsClient.Url = "http://xxx.xxx.xxx.xxx/reportserver/ReportService2010.asmx";
try
{
rsClient.LogonUser("user", "password", "");
Cookie myAuthCookie = rsClient.AuthCookie;
HttpCookie cookie = new HttpCookie(myAuthCookie.Name, myAuthCookie.Value);
Response.Cookies.Add(cookie);
}
Which supposedly would then be used to authenticate the user.
Cookie authCookie = new Cookie(cookie2.Name, cookie2.Value);
authCookie.Domain = "DomainName";
rvSiteMapping.ServerReport.ReportServerCredentials = new MyReportServerCredentials(authCookie);
rvSiteMapping.ServerReport.Cookies.Add(authCookie);
And in my forms authentication within the IReportsServerCredentials Class:-
public bool GetFormsCredentials(out Cookie authCookie,
out string user, out string password, out string authority)
{
authCookie = m_authCookie;
user = password = authority = null;
return true; // Use forms credentials to authenticate.
}
The issue I am experiencing is when the application is passing the credentials to the report server. I believe I must be doing this part incorrectly because while my application does get the cookie, when it authenticates the credentials provided by the cookie, I receive the text/html error:-
Object moved to <a href="/ReportServer/logon.aspx?ReturnUrl=%2fReportserver%2fReportExecution2005.asmx" />
This error is in response to setting a default generic Identity in the event that
the HttpContext.Current.User = null.
if (HttpContext.Current != null
&& HttpContext.Current.User != null)
{
userIdentity = HttpContext.Current.User.Identity;
}
else
{
userIdentity = new GenericIdentity("AnonymousUser");
}
I have tried googling the answer but most of the results are for
windows authentication and the few that are related to forms authentication
are very similar to the code I referred to.
The underlying cause of the issue was under my nose the whole time.
The domain name should refer to the web domain and not the active directory domain.
authCookie.Domain = "DomainName";
The cookie is now able to authenticate the user as intended.
Hopefully this helps anyone who happens to make the same mistake.

Too many OpenID.nonce cookies cause "Bad Request"

I have already gone through links here, here and here which are related to issue I am having.
I have Silverlight application using IdentiServer3 for authentication and I started having this issue just now when I implemented log out functionality. Note that the issue has nothing to do with Silverlight because login and logout functionality is actually implemented on the server side which is a classic ASP.Net Web form. (.NET 4.5.1)
The application never had logout functionality, so user just used to close the browser so we never encountered this issue before. We have now logout.aspx page and Silverlight application have link to this page.
Logout.aspx page
public partial class Logout : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (Request.IsAuthenticated)
{
Session.Clear();
Request.GetOwinContext().Authentication.SignOut();
}
Response.Redirect("/");
}
}
Default.aspx page. This is starting page
public partial class Default : Page
{
protected void Page_Load(object sender, EventArgs e)
{
// Send an OpenID Connect sign-in request.
if (!System.Web.HttpContext.Current.Request.IsAuthenticated)
{
HttpContext.Current.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
}
OWIN startup class where OpenID connection is configured
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
LoginPath = new Microsoft.Owin.PathString("/Default.aspx")
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = ConfigurationManager.AppSettings["Authority"],
Scope = "openid profile",
ClientId = ConfigurationManager.AppSettings["ClientId"],
RedirectUri = ConfigurationManager.AppSettings["RedirectUri"],
ResponseType = "id_token",
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = (context) =>
{
var id = context.AuthenticationTicket.Identity;
// create new identity
var newIdentity = new ClaimsIdentity(id.AuthenticationType);
// we want to keep username and subjectid
var sub = id.FindFirst(ClaimTypes.NameIdentifier);
var username = id.FindFirst("preferred_username");
newIdentity.AddClaim(username);
newIdentity.AddClaim(sub);
// keep the id_token for logout
newIdentity.AddClaim(new Claim("id_token", context.ProtocolMessage.IdToken));
context.AuthenticationTicket = new AuthenticationTicket(
newIdentity,
context.AuthenticationTicket.Properties);
return Task.FromResult(0);
},
RedirectToIdentityProvider = (context) =>
{
if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = context.OwinContext.Authentication.User.FindFirst("id_token").Value;
context.ProtocolMessage.IdTokenHint = idTokenHint;
}
return Task.FromResult(0);
},
}
Steps to reproduce the issue:
I type web site URL which redirects me to identityserver3 login
page.
I enter credentials and hit login.
After successful login I
get redirected to the web site and There I click log out.
I get logged
out successfully. Fiddler shows the following calls
https://idsvr.mydomain.com/identity/connect/endsession?id_token_hint=XXXXXXXXXXXXXX
https://idsvr.mydomain.com/identity/logout?id=616dd9a4e4c6a55b0bb27faceb4df8dd
https://idsvr.mydomain.com/identity/connect/endsessioncallback?sid=xxxxxx
I land up on https://idsvr.mydomain.com/identity/logout?id=xxxxxxx page as expected.
Now I close the browser ( this step is important)
Now type web site URL again which redirects me to identity server login page. ( like Step 1)
I enter credentials and hit login.
After successful login, IdentityServer makes POST to the web site and web site creates AuthenticationTicket. However, here HttpContext.Current.Request.IsAuthenticated is false so Default.aspx page redirects to IdentityServer. I am already logged in so Indetityserver redirects to client web site and loops continue.
Fiddler shows several round trips from identityServer to web site’s default.aspx page. Each roundtrip keeps adding OpenIdConnect.nonce.OpenIdConnect cookie and ultimately i get bad request error because of max request size.
So as suggested in above links I downgraded Microsoft.Owin.Security.OpenIdConnect to 3.0.0 in Client Application.
However, I still get stuck in continuous loop. The only difference is now it does not add new OpenIdConnect.nonce.OpenIdConnect cookie for each round trip. Fiddler shows only one cookie for each round trip. However HttpContext.Current.Request.IsAuthenticated is still false. So I get stuck in continuous loop.
I had a similar issue with my asp.net mvc application . After some Research i found that there is a bug in Microsoft's Owin implementation for System.Web. The one that is being used when running Owin applications on IIS. Which is what probably 99% of us do, if we're using the new Owin-based authentication handling with ASP.NET MVC5.
The bug makes cookies set by Owin mysteriously disappear on some occasions.
This middleware is a fix for that bug. Simple add it before any cookie handling middleware and it will preserve the authentication cookies.
app.UseKentorOwinCookieSaver();
Here is the link in detail
https://github.com/KentorIT/owin-cookie-saver
What solved the problem for me was using AdamDotNet's Custom OpenIdConnectAuthenticationHandler to delete old nonce cookies.
https://stackoverflow.com/a/51671887/4058784

Basic authentication on a remote server

I need some help with ASMX web-services.
Let's suppose I have a ServerService which provides some data. Let's suppose it has a method GetRandomInteger which returns a random integer (obviously). It implements a custom basic authentication using IHttpModule.
public class BasicAuthHttpModule : IHttpModule
{
private UserRepository _userRepository;
public void Dispose()
{
}
public void Init(HttpApplication application)
{
_userRepository = new UserRepository();
application.AuthenticateRequest += OnAuthenticateRequest;
application.EndRequest += OnEndRequest;
}
public void OnAuthenticateRequest(object source, EventArgs e)
{
var app = (HttpApplication)source;
string authHeader = app.Request.Headers["Authorization"];
if (!string.IsNullOrEmpty(authHeader))
{
// Here I successfully get credentials from header
if (_userRepository.ValidateUser(username, password)) return;
// Return 401 and CompleteRequest
}
else
{
// Return 401 and End
}
}
public void OnEndRequest(object source, EventArgs eventArgs)
{
if (HttpContext.Current.Response.StatusCode == 401)
{
// Return 401 and require new authorization
}
}
Fortunately, it works. Now I can successfully open Service.asmx file, get basic authentication window and get access to it's GetRandomInteger method after successful authentication.
Now I have an ASP.NET MVC 4 application called ClientService. It must provide user interface with convenient and appropriate access to methods of ServerService. Now it has default controllers like Account and Home, default views etc.
I need this ClientService to authenticate on a ServerService. I mean there will be a Home/Index page with button "Login". I enter login and password there and ClientService tries to authenticate at ServerService. It returns error on fail or authenticates on success providing access to some Home/RandomInt page which will show the integer requested from ServerService. What is the best and the easiest way to do this?
How to implement registration on a ServerService? There is no AllowAnonymous attribute or something at ASMX, so I can't register user because he doesn't have access to any of methods due to 401 error.
Thank you in advance.
P.S. No. I can't use WCF or something else. I need to implement an ASMX web-service.
Update 1: OK, I have learned something new from here
http://www.aspsnippets.com/Articles/How-to-add-reference-of-Web-Service-ASMX-in-ASPNet-using-Visual-Studio.aspx
There is an old-style thing like "Web reference" and it's not an "Service reference". I have added this Web reference to my project and now I can call some methods from this ASMX page in this way:
try
{
ServerService svc = new ServerService();
svc.Credentials = new NetworkCredential("user", "password");
int a = svc.GetRandomInteger();
} catch (WebException e) {
// Auth failed
}
However, I don't understand how to link it with ASP.NET MVC ClientService authentication. So, both questions are still open. Hopefully, I will understand it or you will help me.
Here is a documentation for adding a Web reference to an ASMX Service.
http://www.aspsnippets.com/Articles/How-to-add-reference-of-Web-Service-ASMX-in-ASPNet-using-Visual-Studio.aspx
Using this information I can easily make requests to a web service.
The only thing I left to do on the moment of question update is to create a custom authentication.
When user logins, the client sends a request to a service. In case of successful basic authentication, it creates proper FormsAuthentication cookie ticket for a user. User logs in.
On each request to a service, the client extracts login from FormsAuthentication cookie and his password from server cache and uses them to authenticate on a service. In case of basic auth failure (it can only occur if user's password has been changed on the service side) the cookie is cleared and session is aborted.
Registration is implemented using another one ASMX service which is not using basic auth but is anonymous (because registration is supposed to be anonymous method).
That's it. Finally, I have found a proper solution :)

EnableCrossAppRedirects - Where is the cross-domain feature documented?

Here an interesting feature of ASP.NET FormsAuthentication explained in this SO answer: How do you pass an authenticated session between app domains
Quick summary; you can create two ASP.NET websites with the same encryption keys. WebsiteA can create a formsauth token, and redirect to WebsiteB with the token in the querystring (or POST body). Switch on EnableCrossAppRedirects in WebsiteB and ASP.NET detects the token and creates the formsauth cookie. In code:
FormsAuthentication.RedirectFromLoginPage("alice", true);
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket("Alice", true, 30);
string encrypted = FormsAuthentication.Encrypt(ticket);
Response.Redirect("http://siteb.dev/Secure/WebForm1.aspx?" + FormsAuthentication.FormsCookieName + "=" + encrypted);
Sounds like a great feature, but where is it documented? I'd feel a bit uneasy using a undocumented feature.
Where I've looked - no mention of this feature in any of the MSDN reference. I thought maybe RedirectFromLoginPage would build a redirect like my code above, it doesn't.
EnableCrossAppRedirects - "is checked within the RedirectFromLoginPage method when the redirect URL does not point to a page in the current application. If EnableCrossAppRedirects is true, then the redirect is performed"
Forms Authentication Across Applications - some advice on setting the machine keys so that a cookie created on a sub-domain, nothing about EnableCrossAppRedirects
forms Element for authentication
Having looked at reflector there is a (somewhat undocumented) feature of forms Authentication. When EnableCrossAppRedirects is enabled .NET will, in addition to looking for the auth cookie, attempt to extract the forms authentication "cookie" from either the form post or the query string. This code is embedded in the FormsAuthentication class in the ExtractTicketFromCookie method, where it can clearly been seen trying to find the authentication cookie in the request data.
if (FormsAuthentication.EnableCrossAppRedirects)
{
text = context.Request.QueryString[name];
if (text != null && text.Length > 1)
{
if (!cookielessTicket && FormsAuthentication.CookieMode == HttpCookieMode.AutoDetect)
{
cookielessTicket = CookielessHelperClass.UseCookieless(context, true, FormsAuthentication.CookieMode);
}
try
{
formsAuthenticationTicket = FormsAuthentication.Decrypt(text);
}
catch
{
flag2 = true;
}
if (formsAuthenticationTicket == null)
{
flag2 = true;
}
}
if (formsAuthenticationTicket == null || formsAuthenticationTicket.Expired)
{
text = context.Request.Form[name];
if (text != null && text.Length > 1)
{
if (!cookielessTicket && FormsAuthentication.CookieMode == HttpCookieMode.AutoDetect)
{
cookielessTicket = CookielessHelperClass.UseCookieless(context, true, FormsAuthentication.CookieMode);
}
try
{
formsAuthenticationTicket = FormsAuthentication.Decrypt(text);
}
catch
{
flag2 = true;
}
if (formsAuthenticationTicket == null)
{
flag2 = true;
}
}
}
}
Therefore if you enable EnableCrossAppRedirects on both applications, then the first application is authorised to redirect to the external site, and the second application will automatically read in the authentication cookie from the request. You just need to engineer it so that the return login URL either posts the cookie data or sends it in the querystring. You also need to be sure that either the machine keys are synchronised, or that the cookie is encrypted using the external apps machine key (by the first app). It seems by default .NET will send the encrypted authentication cookie in the querystring for you and asume your machine keys are in sync (see MSDN quote below).
Here's some more info on MSDN .
If the CookiesSupported property is true, and either the ReturnUrl
variable is within the current application or the
EnableCrossAppRedirects property is true, then the
RedirectFromLoginPage method issues an authentication ticket and
places it in the default cookie using the SetAuthCookie method.
If CookiesSupported is false and the redirect path is to a URL in the
current application, the ticket is issued as part of the redirect URL.
If CookiesSupported is false, EnableCrossAppRedirects is true, and the
redirect URL does not refer to a page within the current application,
the RedirectFromLoginPage method issues an authentication ticket and
places it in the QueryString property.
There is a big warning about the impact on security. EnableCrossAppRedirects is a security setting which prevents ASP.NET login controls from redirecting to an external return URL (another web application). With this setting enabled it can be exploited in some forms of attack - a user is sent to the official login page, but on login is redirected to a different application which they may believe is the same. This is why it's disabled by default.
One way to help mitigate this when enabling the feature is as follows:
To improve security when using cross-application redirects, you should
override the RedirectFromLoginPage method to allow redirects only to
approved Web sites.
You also need to ensure the redirect request is served over SSL to protect the "cookie" in transit, as anyone intercepting would be able to gain control of the account.

Categories