How to properly access HTTP Headers from your controller? (.NET Web App) - c#

I am currently developing a .NET Web App (MVC), and it is designed such that the user logs into the app in another web app, then the IIS passes control to my web app (I call it SP). SP then needs to access headers passed to it containing user information passed from the login page. These fields are then used to add the user to a database for logging purposes. This behavior is called in the constructor from the main controller of the Web App: HomeController because I would like it to run whenever a user accesses the page.
This is my current implementation:
public HomeController()
{
try
{
string ID = Request.Headers.Get("CN userName");
if (ID.Length > 3)
{
try
{
ID = ID.Substring(3, 3);
}
catch
{
ID = ID.Substring(0, 3);
}
}
string firstName = Request.Headers.Get("X-First Name");
string lastName = Request.Headers.Get("X-Last Name");
string email = Request.Headers.Get("X-Mail");
currUser = new User(ID, firstName, lastName, email, ""); }
catch (Exception e)
{
currUser = new User(/*Create Default User*/);
}
}
The User constructor handles methods needed to manage the User after creation. However, this currently throws an error when deployed when it attempts to access any of the headers. I do not have a lot of experience working with headers so I spent hours attempting to find a solution online, but nothing I tried accomplished what I needed it to. I would appreciate assistance in solving this issue and implementing this behavior properly.

I don't think you should do this in a controllers constructor (not sure it's safe either).
It's probably better suited to an Filter, which will make it far easier to reuse.

Related

SOAP error when connecting to NetSuite web services: "Namespace prefix ' soapenv' not defined"

I am getting the following error when connecting to a NetSuite production account, through the Suitetalk API:
I don't have problems connecting to the Sandbox account for this client. I am connecting through a C# WCF project. I don't believe the problem is with the c# project, since this code is being used in Production with many other clients.
It seems to me like the SOAP message being returned is incorrectly formatted - there seems to be a line break before the 'soapenv' element in the SOAP message. I am getting this error when creating a "get" request against the API(using passport login). This error occurs on any API call though, I did try simply logging in through the API as well.
I have double checked the login details and account information for this client and everything seems in orders. Besides, if this information is incorrect, I should be getting authentication errors - not malformed SOAP messages.
Any help will be appreciated, thanks!
It turns out that I needed to use the webservices.na3.netsuite WSDL. I was under the impression that the regular "webservices.netsuite" WSDL would direct any requests to the correct server.
So when connecting to a NetSuite account through SuiteTalk, be sure to make use of the correct WSDL and specify the correct endpoint along with your login credentials. You can check which server your account is hosted on by looking at the URL when logged into your NetSuite account.
Update
I made use of the newest 'DataCenterAwareNetSuiteService' class to dynamically get the correct data center for the current account that I am trying to connect to:
class DataCenterAwareNetSuiteService : NetSuiteService
{
private System.Uri OriginalUri;
public DataCenterAwareNetSuiteService(string account, bool doNotSetUrl)
: base()
{
OriginalUri = new System.Uri(this.Url);
if (account == null || account.Length == 0)
account = "empty";
if (!doNotSetUrl)
{
//var temp = getDataCenterUrls(account);
DataCenterUrls urls = getDataCenterUrls(account).dataCenterUrls;
Uri dataCenterUri = new Uri(urls.webservicesDomain + OriginalUri.PathAndQuery);
this.Url = dataCenterUri.ToString();
}
}
public void SetAccount(string account)
{
if (account == null || account.Length == 0)
account = "empty";
this.Url = OriginalUri.AbsoluteUri;
DataCenterUrls urls = getDataCenterUrls(account).dataCenterUrls;
Uri dataCenterUri = new Uri(urls.webservicesDomain + OriginalUri.PathAndQuery);
this.Url = dataCenterUri.ToString();
}
}
The above is called like so:
new DataCenterAwareNetSuiteService("*account number*", false);
With the latest version of NetSuite, some changes have been made to URLs. For instance, now you can have more than one SandBox URL. Because of this, the URL format has changed. The account number used when authenticating is also now different. For sandboxes the account Id is now passed up as ACCOUNTNUMBER_SANDBOXID, for example 12345678_SB1.
You can determine the URLs for the SOAP and REST services by using the datacenterurls endpoint and supplying the account # you would like to determine the URLS for.
https://rest.netsuite.com/rest/datacenterurls?account=YOUR_ACCOUNT_NUMBER
The functionality below is based on the answer from #Charl above.
I have made a couple changes below that provides the same functionality without using inheritance.
This may be a simpler implementation for a newer programmer who does not know how to use an inherited class.
var accountId = "1234567"; // Insert your account ID here
var Service = new NetSuiteService();
Service.Url = new Uri(Service.getDataCenterUrls(accountId).dataCenterUrls.webservicesDomain + new Uri(Service.Url).PathAndQuery).ToString();

Web API has no session - need to check if user is authenticated

I'm creating my first WebAPI project, and have hit my first snag. It seems that because the WebAPI model is stateless, I have no Session available to me. So, my attempt to add a session variable when logging in, has failed.
public static void CreateSession(int userId, string firstName, string surname, int timezoneOffset, string timezoneName)
{
// Create the object.
var session = new SessionToken
{
FirstName = firstName,
Surname = surname,
TimezoneName = timezoneName,
TimezoneOffset = timezoneOffset,
UserID = userId
};
// Is there an existing session?
var existing = HttpContext.Current.Session[SESSIONNAME];
// If so, we need to kill it and refresh it. Not sure why we would have this case though.
if (existing != null)
HttpContext.Current.Session.Remove(SESSIONNAME);
// Create the session.
HttpContext.Current.Session.Add(SESSIONNAME, session);
}
Session is null, and this is because of the stateless model used by WebAPI.
How can I achieve this with Web API? How can I have something to check and query to see if the current user is valid? My session would normally hold some items such as the chaps name, to render on the layout screen - but it looks like that isn't possible right now.
The recommended approach is using stateless authentication and authorization with tokens.
Since some years, it's very easy to configure your WebAPI to integrate OAuth2 workflow using an OWIN middleware.
Learn how following this tutorial.
What you call session items, in OAuth2 you talk about claims.

How to create an end user SQL Server connection dialog using MVC/C#

I'm new to C# and MVC. Trying to create a intranet application that allows the user to configure or change the initial SQL Server connection string. The connection will need to support SQL Mixed Mode authentication. While my back end skills are exceptional, this is my first attempt at creating a web based intranet web application. I've been searching Google for 3 days trying to find an example, tutorial or documentation with no luck. I've reached a point where I'm not even sure if this is an accepted practice. I'm using VS2015, SQL Server 2012, C#, MVC, ASP.Net and targeting the .Net Framework 4.61. Any guidance is appreciated.
Probably you can't easily change default/initial connection string in web.config dynamically (and considered harmful to change it directly on web.config), however SqlConnectionStringBuilder can build your connection string by request in run-time:
// assume these properties are part of DB provider definition
public class DatabaseSettings
{
public String ConnectionName { get; set; }
public String ServerName { get; set; }
public String DatabaseName { get; set; }
public String UserId { get; set; }
public String Password { get; set; }
}
// controller method definition
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
builder.DataSource = DatabaseSettings.ServerName;
builder.InitialCatalog = DatabaseSettings.DatabaseName;
builder.IntegratedSecurity = true;
builder.UserID = DatabaseSettings.UserId;
builder.Password = DatabaseSettings.Password;
builder.MultipleActiveResultSets = true;
// modify initial connection string in runtime
// note that ConfigurationManager.ConnectionStrings["ConnectionStringName"].ConnectionString is readonly by default,
// thus use reflection to disable private bReadOnly field before adding custom connection string
var connectionString = ConfigurationManager.ConnectionStrings;
var collection = typeof(ConfigurationElementCollection).GetField("bReadOnly", BindingFlags.Instance | BindingFlags.NonPublic);
collection.SetValue(connectionString, false);
// This one may work if you tend to change default connection string value
// connectionString[0].ConnectionString = builder.ToString();
// Safer way by adding new name rather than replace default one
connectionString.Add(new ConnectionStringSettings(DatabaseSettings.ConnectionName, builder.ToString()));
AFAIK you can store user-defined connection string inside XML file for each user and load programmatically when someone requires it (see Reading connection string from external config file).
Regarding mixed mode (Windows & Forms authentication for both intranet & internet), setup your IIS to 2 entrypoints (i.e. virtual directories), where complete app should rely on Forms authentication mode. The controller of Windows authentication simply redirect to main site by passing user identity.
On your main site's login page, you may want to add "Login with Windows/Active Directory account" button (similar to login button with existing social media accounts) and clicking it will redirect intranet users to a controller specifically crafted to receive credentials from Windows authentication mode.
Perhaps this is not the best way to answer your needs, but at least may open your mind to learn and figure what things to do.
References:
1) Change connection string programmatically
http://david.gardiner.net.au/2008/09/programmatically-setting.html
2) Dual-mode authentication
https://msdn.microsoft.com/en-us/library/bb669066(v=vs.110).aspx (MS SQL Server authentication article)
http://www.codeguru.com/csharp/.net/net_security/authentication/article.php/c19559/ASPNET-Mixed-Mode-Authentication.htm
http://mvolo.com/iis-70-twolevel-authentication-with-forms-authentication-and-windows-authentication/

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 :)

Side by side Basic and Forms Authentication with ASP.NET Web API

Disclaimer: let me start by saying that I am new to MVC4 + Web Api + Web Services in general + JQuery. I might be attacking this on the wrong angle.
I am trying to build a Web MVC App + Web API in C# for .NET 4 to deploy in Azure. The web api will be used by mobile clients (iOS, using RestKit).
The Web MVC App will be relatively simple. We would like to use Forms Authentication for it and SimpleMembership - which we achieved and works fine.
We'll use the Web API methods from JQuery (Knockout) scripts to fill pieces of the web pages. Therefore, we expect the JQuery to use the same identity authenticated by Forms Authentication.
However, the idea is that the Web Api can be called directly by mobile clients. No Forms Authentications for those.
We have been looking at the Thinktecture Identity Model (http://nuget.org/packages/Thinktecture.IdentityModel https://github.com/thinktecture/Thinktecture.IdentityModel.40). We added the BasicAuth and AcessKey handlers to the config and it works (see code below).
When you try to access the webapi without being authenticated the browser displays the basic authentication dialog and works as expected.
The "issue" is that when you ARE already logged in via Forms Authentication and try to call a Web Api method you still get the Basic Authentication dialog. In other words, Thinktecture IdentityModel seems to ignore the Forms Authentication altogether.
My questions are:
Are my expectations correct? that once I have done the forms authentication I shouldn't do anything else to let the JQuery scripts, etc., access the Web API from the same browser user session.
How do I fix it?
If my expectations are not correct; how is this supposed to work? ie: how do I make the JQuery scripts authenticate?
I know there are tons of similar questions in Stackoverflow, I honestly looked a lot of up, saw videos, etc., but either I am missing something obvious or there is no clear documentation about this for somebody new in the technologies.
I appreciate the help. Thanks.
public static AuthenticationConfiguration CreateConfiguration()
{
var config = new AuthenticationConfiguration
{
DefaultAuthenticationScheme = "Basic",
EnableSessionToken = true,
SetNoRedirectMarker = true
};
config.AddBasicAuthentication((userName, password) => userName == password, retainPassword: false);
config.AddAccessKey(token =>
{
if (ObfuscatingComparer.IsEqual(token, "accesskey123"))
{
return Principal.Create("Custom",
new Claim("customerid", "123"),
new Claim("email", "foo#customer.com"));
}
return null;
}, AuthenticationOptions.ForQueryString("key"));
Here is the solution for this problem which I have come up with earlier.
Note: This solution doesn't involve Thinktecture Identity Model.
I have an abstract BasicAuthenticationHandler class which is a delegating handler. You can get this handler by installing the latest stable WebAPIDoodle NuGet package.
You can give a hint to this base basic authentication handler to suppress the authentication process if the request has been already authentication (e.g: by forms auth). Your custom handler that you need to register would look like as below:
public class MyApplicationAuthHandler : BasicAuthenticationHandler {
public MyApplicationAuthHandler()
: base(suppressIfAlreadyAuthenticated: true) { }
protected override IPrincipal AuthenticateUser(
HttpRequestMessage request,
string username,
string password,
CancellationToken cancellationToken) {
//this method will be called only if the request
//is not authanticated.
//If you are using forms auth, this won't be called
//as you will be authed by the forms auth bofore you hit here
//and Thread.CurrentPrincipal would be populated.
//If you aren't authed:
//Do you auth here and send back an IPrincipal
//instance as I do below.
var membershipService = (IMembershipService)request
.GetDependencyScope()
.GetService(typeof(IMembershipService));
var validUserCtx = membershipService
.ValidateUser(username, password);
return validUserCtx.Principal;
}
protected override void HandleUnauthenticatedRequest(UnauthenticatedRequestContext context) {
// Do nothing here. The Autharization
// will be handled by the AuthorizeAttribute.
}
}
As a final step, you will need to apply System.Web.Http.AuthorizeAttribute (not System.Web.Mvc.AuthorizeAttribute) to your controllers and action methods to give authorization for the specific roles and users.
I hope this helps to solve your problem.

Categories