Authenticating ASP.NET web app with WCF service - asp.net

Background
I have an ASP.NET web application that interacts with WCF services. The web application and the WCF services are under my control. The ASP.NET web application uses a custom implementation of the ASP.NET Membership Provider Model (with passwords stored in hashed form) to authenticate users who log in to the web application. Both the ASP.NET web application and WCF services have access to the same membership database.
Since the users will supply their password only once, and I don't want to have to store their passwords anywhere or annoy them by repeatedly asking them to resupply their password, I need an appropriate mechanism to authenticate the users with the WCF services.
Based on other questions and answers I have seen, I am considering a sort of "login session" approach, where a login session will be created in the custom membership database when the user initially logs in to the web application, with the login session identified by a GUID, and automatically expired after a period of inactivity. The login session GUID will be "remembered" by the web application for each logged in user (either stored in the Forms Authentication Ticket or in the session).
The WCF service will also provide its own login operation accepting a user name and password and returning a login session GUID as described above.
The WCF service would then accept the login session GUID for all other operations, and verify that the GUID represents a valid login session that has not expired before allowing the operation to proceed.
I have done quite a bit of background reading on this, and there is a lot of material on straightforward use of the UserName client credential type, but this would require the web application to remember the user's password, which doesn't seem like a great idea to me.
I've done some research and found material on MSDN, but this seems like a lot of effort to implement what (to me at least) seems like a pretty common usage scenario.
How to: Create a Custom Token
Question
Is the general approach of the "login session" described above reasonable?
If so, what is the best way to achieve it?
If not, can you suggest an alternative?

This is a very reasonable approach.
To do this you setup your service endpoint and configure it with your custom membership provider (You can do the same with SQL membership provider, it doesn't require a custom one).
On the web application you set up the Authenticate event of the Login control to instantiate a new service proxy and set the username/password in the ClientCredentials in the proxy.
Now when you make the call to the Service through the proxy WCF will pass these credentials through the secure channel to the service and use them for authentication.
Now you simply need to store the proxy in session and use it for future access to the service as it has the channel state and a private key.
protected void LoginControl_Authenticate(object sender, AuthenticateEventArgs e)
{
bool Authenticated = false;
try
{
MyServiceClient proxy = new MyServiceClient("MyServiceEndpoint");
proxy.ClientCredentials.UserName.UserName = LoginControl.UserName;
proxy.ClientCredentials.UserName.Password = LoginControl.Password;
//It doesn't really matter what is called or what it does because
//Membership Provider for the Service does the authentication.
string retval = proxy.login("Logging in");
//Now that channel is established the proxy needs to be kept
//since it contains the channel state which includes a private key
Session["MyServiceProxy"] = proxy;
Authenticated = true;
}
catch (Exception ex)
{
//Login Error...
}
e.Authenticated = Authenticated;
}

There are two possible solutions I can think of:
Firstly, if the WCF service is an internal service, the web application can then send the name of the user that is asking for the data with each request.
The second is that you store the user name and hash of the password (or actual password) somewhere. Either in the session state or in the user cookie (a session cookie stored in memory passed to the user over https). Then pass the user name and password to the WCF service with each request.

See my answer on Storing password in forms authentication cookie - ASP.NET and WCF calls for a solution that does not require storing passwords.

Thanks to everyone for your input. I've settled on an approach (at least for now). It's fairly simple and works well for my purposes.
Using the "UserName" clientCredentialType, and an explicit service login method that returns a security token (token generation details omitted for brevity), the service client can decide whether to pass the genuine password as the password property on the client credentials or the security token instead (obtained from the login method). If the client is a web application the security token could be stored in the forms authentication ticket, or the session, or wherever.
Using the "Custom" userNamePasswordValidationMode and a custom implementation of UserNamePasswordValidator, the validator inspects the password to determine whether it is a valid security token or a password - if it's a password the client credentials are authenticated against the user store (SQL Server database), and if it's a security token then it is checked to ensure it is valid, hasn't expired, and belongs to the user name.

I would suggest to take a look at Geneva which is aimed at solving scenarios as yours. The basic idea is to require the same security token, by mean of an HttpModule, for both the WCF services and the ASP site. The token will be release after authenticating against your membership database and may contain useful info (claims) on the user.
For an intro you may read Bustamante's article.

Microsoft has a WCF service that you can use to authenticate users with ASP.NET Membership.
The code is actually built into the framework - you just need to create a .svc file to use it.
http://msdn.microsoft.com/en-us/library/bb398990.aspx

Related

How does Blazor Webassembly app know which user has been logged in server side?

I have created a new Blazor WebAssembly project with a separate API project. My WebAssembly project will
query this WebAPI for data.
I have included Identity in my app and this seems to work. I can register a new user and use the below code to log in a user on my Web API.
Code used to log someone in:
var result = await _signInManager.PasswordSignInAsync(model.UserName, model.Password, false, lockoutOnFailure: false);
if (result.Succeeded)
{
//I arrive here, so login was succesful
return true;
}
So Identity seems to be working.
What I'm wondering now is:
my user logs on in the web assembly app
this app uses HttpClient to send a request to my server to authenticate the user, using a username and password
the server checks the username and password and uses _signInManager.PasswordSignInAsync(username, password) which works
How does my client know which user has been authenticated? How can I display more information about this user, check his roles, ...
Does anyone have any guidance or best practices on this please?
Since Blazor WASM is entirely executed on the client, there are two different authentication models: Cookies and Token. (There are more). The basic idea, though, is the same. With each HTTP request sent by HttpClient, an additional header is sent to the server.
Personally, I think Token authentication is a much smarter way supported in different libraries and can be implemented very quickly.
In a nutshell:
With a token-based approach - to be more precise with OpenId Connect -,, you provide an identity service, and your Blazor application will become a client. The token, unlike a cookie, can be read easily by a client. If configured properly, it contains information (claims) like the user's name or email address.
The identity service is an MVC/Razor page application with special libraries handling the authentication flow.
The authentication flow (very, very briefly):
A user wants to login into your Blazor application. If there is no token available, the authentication process starts. The Blazor application redirects the user to the "authentication flow" part of your identity service. It is an endpoint without any "visual" result. The result is another redirect to your MVC/Razor page login view. You can implement any authentication logic here. If the user has been authenticated successfully (based on your logic), the user is redirected back to your Blazor application with a valid token in the URL. This token is extracted and can be used to access an API.
It sounds a bit complicated, and it is a lot to learn and understand. However, you will get a reliable, widely used, and safe way to do authentication and authorization.
As a starting point for reading
https://identityserver4.readthedocs.io/en/latest/quickstarts/0_overview.html
https://identityserver4.readthedocs.io/en/latest/topics/signin.html#login-workflow
https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/standalone-with-authentication-library?view=aspnetcore-5.0

If/When use "Azure Session State Provider (redis)" No need to use User.Identity.Name/IsAuthenticated?

After we have moved to Azure farm, I have implied Azure Session State Provider (redis) to my asp.net mvc application, But some Authorized pages redirect me to login page !!
Is that because I use User.Identity.Name or User.Identity.IsAuthenticated in some actions!!
Do I have to replace the User.Identity.Name with :
// instead of below line
//Boolean usern = User.Identity.IsAuthenticated;
// is below lines :
Boolean usern = "";
object objValue = Session["usersession"];
if (objValue != null)
{ usern = true;}
else {usern = false;}
Is that right, if not why users redirect to login again sometimes !!!
This is probably not a session issue but rather an authentication cookie/ticket issue. Azure most likely has their servers load balanced, even if you are only using a single instance/role(this gives them reliability %). Meaning that your application actually exists on more than one server at a time.
The MachineKey in a .NET app is what is responsible for encrypting and decrypting your authentication cookie. In your web.config, if you are not properly defining the <machineKey> attribute, then IIS makes up a machine key for you. Each server running the application will make their own machine key if it is not defined by you. As a result, one server is able to decrypt and read your authentication ticket, while the next request goes to another server which cannot decrypt the authentication ticket because it was encrypted with a different key and this server thinks that you are not logged in.
To address this issue, open your web.config file and define your <machineKey> attribute and redeploy. Once you login with the newly deployed application, you should see this issue disappear.
Forms authentication and Machine Key information on MSDN
Machine Key Generator
I don't think the session management would do this, regardless of Azure Redis or otherwise. If you are using asp.net authentication cookies you need to make changes to support multiple roles.
I know this is an old article but if you look through Moving Applications to Microsoft Azure Cloud Services you will see this:
There is one further change to the application that potentially
affects the authentication process. If you were to run the aExpense
application on more than one web role instance in Azure, the default
cookie encryption mechanism (which uses DPAPI) is not appropriate
because each instance has a different key. This would mean that a
cookie created by one web role instance would not be readable by
another web role instance. To solve this problem you should use a
cookie encryption mechanism that uses a key shared by all the web role
instances. The following code from the Global.asax file shows how to
replace the default SessionSecurityHandler object and configure it to
use the RsaEncryptionCookieTransform class.
essentially if this is happening the redirects to the login page are because you are bouncing to a different instance and it could not read the asp.net auth cookie and just assumed the user was not authenticated as a result.

How to protect a WCF Rest service with username and password?

I'm new in WCF and I want to know how can I protect a WCF Rest service.
I have an asp.net website, only registered users can access it, the application uses a service hosted on the same IIS server, my question is, how can I restrict the use of this service, for that only registered users may use it, knowing that the service can be used by many clients (Android, iPhone, ...). what type of authentication I can use? to test the service I created a winform and I use an HttpWebRequest.
PS: I cant use https.
Thanks
Simplest way is to use asp.net compatibility mode. The WCF service call will result in the same preprocessing used for ASP.NET pages, including checking the ASP.NET auth and session cookies. You will also be able to check HttpContext, including httpcontext.current.user.identity.isauthenticated. If the user is not authenticated, throw an exception or return an error code. Here is some more information: http://msdn.microsoft.com/en-us/library/aa702682.aspx.
So if you are already using forms auth for your application, and the service should be called after a user has logged in to your application, you are set.
You can also create an authentication service. The service will allow the client to send a username / password, and will use ASP.NET authentication to authenticate the user. It will send back an auth cookie, and then you can check future service calls as above. See http://msdn.microsoft.com/en-us/library/bb386582.aspx.
I believe the authentication service can called using json. See How to Call .NET AuthenticationService from json client without ASP.NET.

Obtaining an ICredentials object for the authenticated user of an ASP website

I'm attempting to add a feature to an existing ASP.net web site that needs to pull the users calendar data from the Exchange Web Service. I know that this is possible, and have local tests running as a proof of concept, but I'm having some issues in implementing this in the context of an ASP website.
My intention is to override the default credentials by constructing an instance of the WebCredentials class using the current user of the ASP sites credentials and then using this to create an Exchange Service Binding.
Example
ExchangeService service = new ExchangeService(ExchnageVersion.2007);
service.AutoDetect(emailAddress);
service.Credentials = new WebCredentials(credentials);
Unfortunately, the WebCredentials object can only be constructed from either a set of strings containing username, password and domain, or by passing in an ICredentials object.
So, the question is, within a Forms authenticated web site, is there any way to obtain an ICredentials object for the currently authenticated user?
No forms authentication uses an bespoke encrypted cookie which is sent by the browser with every request. It let's ASP.NET know that the user is authenticated and who they are. It's not designed to work with external systems, and in fact it requires the machinekey on the server to be able to decrypt the details. Passing forms credentials to another server would be pointless.
This system is different to Windows auth which can use various authentication schemes such as basic, digest, NTLM, and Kerberos authentication to negotiate credentials as part of the HTTP protocol, and which can be understood by external systems on the same Exchange.
So to use ICredentials you either need to be using Windows authentication in your ASP.Net application, or have access to username/password so that you can use NetworkCrententials explicitly.

Passing Custom User Object to WCF

I've implemented a custom ASP.net membership provider to deal with forms authentication. The custom provider uses a custom User object for authentication and authorization. I was wondering If I can pass this object to each WCF call without adding it to the parameters list?
Since you are already using a MembershipProvider you can utalize that on wcf as well so both are secured by the same mechanism.
See this post on msdn.
Windows Communication Foundation (WCF)
developers can take advantage of these
features for security purposes. When
integrated into an WCF application,
users must supply a user name/password
combination to the WCF client
application. To transfer the data to
the WCF service, use a binding that
supports user name/password
credentials, such as the WSHttpBinding
(in configuration, the wsHttpBinding
Element) and set the client credential
type to UserName. On the service, WCF
security authenticates the user based
on the user name and password, and
also assigns the role specified by the
ASP.NET role.
Another option would be to create a custom IAuthorizationPolicy that pulls off your user via
OperationContext.Current.IncomingMessageHeaders.GetHeader<T>
And than setup your principal like the following:
evaluationContext.Properties[Constants.EvaluationContextPrincipal] = principal;
Here is some more information on creating a custom IAuthroizationPolicy. With this method you could achieve what you want without passing your user to the method.
Just be warned if you go this route a crafty person could end up impersonating the user by simply suppling a bogus user in your header.
Using the asp.net membership provider for wcf would most likely get you what you are really after plus adding some security.
You definitely should not add this to the parameters each method.
I do not know about your custom user object but as far as WS* and most security standards concerned, your user object will have username and password.
The answer depends on the binding you use. BasicHttpBinding or BasicHttpContextBinding can use HTTP authentication schemes while WsHttpBinding can use custom Message security which you can provide user name and password.
BasicHttpContextBinding is especially good since it can work with ASP NET session.

Resources