It’s a fairly common requirement that a business service authenticates a client and it’s usually (at least) for the purpose of authorisation whereby we can control which users have access to an application or to some of its functionality.
The two ways you usually go about it with a web site or web service are;
• integrated – i.e. let the web server do it via something like Basic Authentication, Digest Authentication, Windows Authentication.
• “forms” – i.e. the web server leaves the traffic well alone and something like ASP.NET steps in to make sure that each request carries an appropriate token (cookie) indicating that it has been authenticated. Unauthenticated traffic is usually redirected to a “login page” which harvests credentials and returns a suitable cookie to be replayed on subsequent requests.
Usually, the former is done against credentials managed by Windows whereas the latter is done against credentials managed by the application itself (or by ASP.NET on its behalf).
Where “forms” authentication can get a little odd is where you are using it to authenticate calls to web services and there perhaps aren’t any actual web pages in your application at all.
For instance, you can easily build a Silverlight client that calls web services and then put forms authentication in front of those services. This means that the first request from the client to a service is going to be bounced as it won’t be carrying the right cookie.
That kind of client needs some additional service to call to perform the authentication that’s not an HTML based web page and that’s made easy by ASP.NET offering its membership/roles/profiles services as web services (which I made use of here) so a Silverlight client can call the membership service, get credentials authenticated and then (usually) that translates into a cookie that then authenticates the client as it makes follow-on service calls.
But, that all takes a bit of work to get things in place and so RIA Services comes along and simplifies it by supporting both Windows (i.e. integrated) and Forms based authentication and any other custom scheme that you might want to plug in.
How do they do that then? Abstraction!
RIA Services already has the notion of a service than can be easily exposed to the client side – the DomainService.
In order to support authentication, there’s a derivation of DomainService called AuthenticationBase<T> on the server side. Because it’s a DomainService, the code-generation process will make it available to the client side.
AuthenticationBase<T> looks like;
The two ways you usually go about it with a web site or web service are;
• integrated – i.e. let the web server do it via something like Basic Authentication, Digest Authentication, Windows Authentication.
• “forms” – i.e. the web server leaves the traffic well alone and something like ASP.NET steps in to make sure that each request carries an appropriate token (cookie) indicating that it has been authenticated. Unauthenticated traffic is usually redirected to a “login page” which harvests credentials and returns a suitable cookie to be replayed on subsequent requests.
Usually, the former is done against credentials managed by Windows whereas the latter is done against credentials managed by the application itself (or by ASP.NET on its behalf).
Where “forms” authentication can get a little odd is where you are using it to authenticate calls to web services and there perhaps aren’t any actual web pages in your application at all.
For instance, you can easily build a Silverlight client that calls web services and then put forms authentication in front of those services. This means that the first request from the client to a service is going to be bounced as it won’t be carrying the right cookie.
That kind of client needs some additional service to call to perform the authentication that’s not an HTML based web page and that’s made easy by ASP.NET offering its membership/roles/profiles services as web services (which I made use of here) so a Silverlight client can call the membership service, get credentials authenticated and then (usually) that translates into a cookie that then authenticates the client as it makes follow-on service calls.
But, that all takes a bit of work to get things in place and so RIA Services comes along and simplifies it by supporting both Windows (i.e. integrated) and Forms based authentication and any other custom scheme that you might want to plug in.
How do they do that then? Abstraction!
RIA Services already has the notion of a service than can be easily exposed to the client side – the DomainService.
In order to support authentication, there’s a derivation of DomainService called AuthenticationBase<T> on the server side. Because it’s a DomainService, the code-generation process will make it available to the client side.
AuthenticationBase<T> looks like;
the idea of the generic parameter <T> here is that it’s a class to represent the authenticated user down on the client side and IAuthentication<T> providing the plug-in point.
Whatever information you need to pass around about the user can be added to this class and then it’ll show up down on the client side. The primary example here would be properties that you’re storing in the ASP.NET profile system and there’s an automatic way to wire those up (later). Anyway, you’re more than likely to want to know the user’s name and application roles on the client side so T here is constrained to be an IUser which looks as above;
Running the code generation process on these server-side bits causes client side bits to get emitted;
• a User class that’s derived from Entity, but also implements IPrincipal, IIdentity
o I talked about Entity a lot in this previous post – given that this is data returned from a DomainService, it makes sense that it derives from Entity
• A class derived from AuthenticationDomainContextBase
at first, it doesn’t seem so obvious what you actually do with the AuthenticationDomainContextBase class unless you’ve seen these other classes;
So…we can do FormsAuthentication or WindowsAuthentication and we can treat either of these as an AuthenticationService and in doing so we need to plug in a DomainContext of type AuthenticationDomainContextBase and so that’s where our generated class plugs in.
AuthenticationService looks like;
and so I can write code that uses FormsAuthentication/WindowsAuthentication directly or I can take advantage of some more generation…
If you’ve got a direct link from your Silverlight project to your Web project then you’ll have a generated WebContext class (derives from WebContextBase) and that class;
• implements IApplicationService which means you can drop it into your Application class’s ApplicationLifetimeObjects collection
• has a static member on it called Current which returns the current WebContext so you can easily grab it from anywhere
• has a property on it called Authentication of type AuthenticationService so this provides a slot where it’s easy to store/retrieve whatever authentication service you’re using
if you’ve used the “Silverlight Business Application” template then you’ll have seen that this template automatically instantiates a WebContext for you ( check out App.xaml.cs ) and automatically instantiates a FormsAuthentication instance and drops it into the WebContext’s property Authentication. You could equally well do a similar thing in XAML or code yourself like this XAML example below;
and with that in place it means I can use WebContext.Current.Authentication to get to my AuthenticationService whenever I want.
If you don’t have a direct link then it’s pretty easy to create a WebContextBase derived class yourself.
Either way, I’ve got an AuthenticationService (FormsAuthentication/WindowsAuthentication) that I can write code against. So, how’d you make this work in specific scenarios around both Forms authentication and Integrated authentication?
Now say I have a following service; and so I can call this from the client-side;
but, having done nothing about authentication I hit an immediate problem which looks to resolve to;
but I’m not trying to authenticate! What’s going on here? Well, if I look at my IIS set up then what I have is;
• Default Web Site->Auth Site
and I have both of those set up to allow both anonymous and basic authentication;
• Default Web Site->Auth Site
and I have both of those set up to allow both anonymous and basic authentication;
and that’s causing me “a problem”. As far as I know, the problem it’s actually causing is to the webHttpBinding which is trying to “guess” which of these 2 options I want it to use. What if I change my configuration to try and help it out?
That seemed to stop WCF trying to guess about whether I wanted “Anonymous” or “Basic” and I’m now running unauthenticated calls from my client to my service.
I guess it’s time to switch on some authentication then…
Requiring Authentication
I can declaratively demand that access to my entire service or specific operations is authenticated by applying RequiresAuthenticationAttribute and be specific about roles (defined by ASP.NET roles/Windows) by using the RequiresRoleAttribute and I can do that to either the entire domain service class or specific methods on it as in;
I guess it’s time to switch on some authentication then…
Requiring Authentication
I can declaratively demand that access to my entire service or specific operations is authenticated by applying RequiresAuthenticationAttribute and be specific about roles (defined by ASP.NET roles/Windows) by using the RequiresRoleAttribute and I can do that to either the entire domain service class or specific methods on it as in;
and now my client can no longer make calls to that particular operation ( fails with an “Access Denied” error as you’d expect ) so I need to get my client to authenticate.
On the server-side, I can switch on Forms authentication in my web.config;
On the server-side, I can switch on Forms authentication in my web.config;
and then I figured that I might be able to get away with just using the FormsAuthentication class on the client side without even making use of an Authentication Domain Service because I’m not doing anything fancy with users and so on. So, I tried;
but that’s no good. I get an exception;
The DomainContextType is null or invalid and there are no contexts generated from AuthenticationBase<T>.
Ok – fair enough. I add a new project item;
The DomainContextType is null or invalid and there are no contexts generated from AuthenticationBase<T>.
Ok – fair enough. I add a new project item;
to add a Authentication Domain Service to my service-side library project (AuthRIALibrary.Web) and called it MyAuthDomainService and that causes the generation of a MyAuthDomainContext on the client side and then I can authenticate and (on success) call my service operation;
and that works fine for me. Now, because of the way I’ve structured my projects I don’t get a WebContextBase-derived class generated for me in my main Silverlight application project but I can duplicate the generation process easily enough in that project;
then I can ( maybe ) drop some of this into my App.xaml;
and then that makes the rest of my code a lot simpler;
( although not exactly equivalent code to what I did before but the intent is the same and now I can rely on MyWebContext.Current and not worry too much about what kind of Authentication service it’s providing ).
It’s not a great idea to do Forms authentication over HTTP though so it’d be better to make sure that I change the MyAuthDomainService class to state that it needs HTTPS as a requirement for use;
It’s not a great idea to do Forms authentication over HTTP though so it’d be better to make sure that I change the MyAuthDomainService class to state that it needs HTTPS as a requirement for use;
and then my client code stopped working because it was trying to access the forms authentication service via https://localhost and that’s not going to work.
How did the client suddenly know it was meant to use HTTPS for access to the authentication domain service? A sneaky small trick – if you have RequiresSecureEndpoint=true set then the client code generation process spits out a slightly different constructor for your class derived from AuthenticationDomainContextBase. In my case, this is my MyAuthDomainContext class and the modification in the generated code is;
How did the client suddenly know it was meant to use HTTPS for access to the authentication domain service? A sneaky small trick – if you have RequiresSecureEndpoint=true set then the client code generation process spits out a slightly different constructor for your class derived from AuthenticationDomainContextBase. In my case, this is my MyAuthDomainContext class and the modification in the generated code is;
that true flag in the generated code says “Use HTTPS” and isn’t present if the RequiresSecureEndpoint isn’t set on the server.
How do I get my client code back to working? Up until now I’d be letting Visual Studio set the start page for my solutions but this means that it uses http://localhost so I need to switch that start page because localhost isn’t a machine name that I have SSL certificates for. My machine name is kumardh and I have certificates for that name.
I switched my start page for the application to have a proper machine name in the URL http://kumardh/… and then (not surprisingly) I hit a cross-domain issue because my application is being served up over HTTP and it straight away goes and does this HTTPS request in order to do its Forms authentication via MyAuthDomainContext.
How do I get my client code back to working? Up until now I’d be letting Visual Studio set the start page for my solutions but this means that it uses http://localhost so I need to switch that start page because localhost isn’t a machine name that I have SSL certificates for. My machine name is kumardh and I have certificates for that name.
I switched my start page for the application to have a proper machine name in the URL http://kumardh/… and then (not surprisingly) I hit a cross-domain issue because my application is being served up over HTTP and it straight away goes and does this HTTPS request in order to do its Forms authentication via MyAuthDomainContext.
and that’s enough to say that apps from my machine are allowed to do cross-scheme into my AuthSite virtual directory.
On the service-side, I can get hold of the authenticated user easily enough;
On the service-side, I can get hold of the authenticated user easily enough;
just like I can easily get to the user on the client side via MyWebContext.Current.User (see previou lumps of code).
If I also want to program against the Profile service from ASP.NET and store data on a per-user basis without having to write custom code then I can (e.g.) switch on the service in my web.config and add a per-user property such as;
If I also want to program against the Profile service from ASP.NET and store data on a per-user basis without having to write custom code then I can (e.g.) switch on the service in my web.config and add a per-user property such as;
and then add ( service-side in my RIAAuthLibrary.Web project ) to my User class which Visual Studio generated when I added an Authentication Domain Service;
and then that becomes available to me from the client side ( minus any kind of error handling );
so that’s all good and I’m doing read/write on those properties and persisting the changes back to the server. What about integrated authentication?
Integrated Authentication
I like to start simple so to experiment with integrated authentication I first backed out the HTTPS requirement on my service;
Integrated Authentication
I like to start simple so to experiment with integrated authentication I first backed out the HTTPS requirement on my service;
and then I edited my web.config in order to specify that I was using Windows authentication and that I wanted to use Basic authentication along with that; and also to guide WCF;
then I made sure that my IIS configuration was set up to allow (not force) Basic authentication for my site;
Then I changed my App.xaml to use WindowsAuthentication rather than FormsAuthentication;
Then I hit a snag when running my client side code because running it produces an exception;
A first chance exception of type 'System.NotSupportedException' occurred in System.ServiceModel.DomainServices.Client.Web
Additional information: Windows authentication does not support logging in.
so it seems like if you’re using integrated authentication then you are going to let the “transport” figure out the credentials rather than providing them programmatically and so you don’t explicitly Login with a user name and password.
Silverlight has 2 stacks for HTTP – client and browser with different capabilities. It’s a trade-off as to which one you use. Specifically, here, the client stack is capable of doing integrated authentication by either allowing the transport to gather credentials or by having them provided by code. The browser stack does not support the idea of credentials coming from code. I suspect (to get ASP.NET cookie integration) that RIA services is running over the browser stack and so you can’t pass credentials when doing an integrated authentication.
I need to change my client-side code a little in order to call LoadUser rather than Login – it’s “fun” to note that even though I’ve moved to integrated authentication, I can still rely on the ASP.NET profile service and get custom per-user data represented on the client side by the generated User class and so most of my code still works if I call LoadUser() instead of Login;
MyWebContext.Current.Authentication.LoadUser();
Personally, I’d perhaps want to put the difference between FormsAuthentication and WindowsAuthentication behind some other class that abstracts the differences here around Login/LoadUser.
When I run that code, I see the browser prompt;
Personally, I’d perhaps want to put the difference between FormsAuthentication and WindowsAuthentication behind some other class that abstracts the differences here around Login/LoadUser.
When I run that code, I see the browser prompt;
and then the client-server interaction happens and I can see in Fiddler what’s going on;
Great. I can switch to using Windows authentication by changing my web.config;
and re-configuring IIS;
and that seems to work fine – it does not involve me typing in credentials because (in my setup) my web server and my client machine are in the same domain and I’m already authenticated so Windows just does the “right thing” here.
Note – I also tried changing my configuration to “digest” here and for whatever reason I couldn’t get that to work – not sure that’s going on with my configuration there.
Now, naturally, if I’m going to use something like “Basic” then I need to secure it with HTTPS.
So, I switched my configuration back to “basic” and then altered my authentication service to demand HTTPS; and in web.Config;
Note – I also tried changing my configuration to “digest” here and for whatever reason I couldn’t get that to work – not sure that’s going on with my configuration there.
Now, naturally, if I’m going to use something like “Basic” then I need to secure it with HTTPS.
So, I switched my configuration back to “basic” and then altered my authentication service to demand HTTPS; and in web.Config;
and then things went slightly awry for a while in that I hit an exception;
Additional information: Could not find a base address that matches scheme https for the endpoint with binding WebHttpBinding. Registered base address schemes are [http]
and it took me a little while to figure out how to get past it and I’m not entirely sure that I got past it in the right way.
What I did was set the security mode up there back to None.
I’m not nearly 100% sure on this one. I’ve told the client to always use HTTPS so that seems fine but with mode=”None” it feels like I might be allowing my service to be called over HTTP but then I’ve got the RequiresSecureEndpoint setting set to true. So am I configuring this the right way? Not sure!
I’ll come back to this if I figure it out better.
Providing a .SVC File
Up until now, for integrated authentication I’ve been setting up IIS so that my site is using;
• Anonymous + Basic
• Anonymous + Windows
with the rationale being that I want the unauthenticated user to be able to get to my HTML page and my Silverlight XAP but then I want to have my Silverlight application’s calls across to my domain services to be authenticated and I’ve been using the standard webHttpBinding in my web.config to ensure that WCF knows whether I’m actually using Basic/Windows;
Additional information: Could not find a base address that matches scheme https for the endpoint with binding WebHttpBinding. Registered base address schemes are [http]
and it took me a little while to figure out how to get past it and I’m not entirely sure that I got past it in the right way.
What I did was set the security mode up there back to None.
I’m not nearly 100% sure on this one. I’ve told the client to always use HTTPS so that seems fine but with mode=”None” it feels like I might be allowing my service to be called over HTTP but then I’ve got the RequiresSecureEndpoint setting set to true. So am I configuring this the right way? Not sure!
I’ll come back to this if I figure it out better.
Providing a .SVC File
Up until now, for integrated authentication I’ve been setting up IIS so that my site is using;
• Anonymous + Basic
• Anonymous + Windows
with the rationale being that I want the unauthenticated user to be able to get to my HTML page and my Silverlight XAP but then I want to have my Silverlight application’s calls across to my domain services to be authenticated and I’ve been using the standard webHttpBinding in my web.config to ensure that WCF knows whether I’m actually using Basic/Windows;
If I was using WCF on its own and wanted to do something like basic authentication over HTTPS what I usually do is drop my service SVC files into a folder called something like secure and then I set up IIS to demand HTTPS and basic authentication for that particular folder (and I config up WCF to tell it that’s what I’m doing).
But how to do that with RIA Services where there are no SVC files to drop into a folder?
Whilst RIA Services usually dynamically generates a .SVC file (see earlier post) you can author them manually yourself.
If I create a folder called Services in my web site then I can create SVC files for my services ( the authentication service and the domain service ) naming them in my case;
But how to do that with RIA Services where there are no SVC files to drop into a folder?
Whilst RIA Services usually dynamically generates a .SVC file (see earlier post) you can author them manually yourself.
If I create a folder called Services in my web site then I can create SVC files for my services ( the authentication service and the domain service ) naming them in my case;
and the content of one of those files looks like; and I can alter my main web.config to remove the service definitions;
and then I can have the web.config in the Services sub-folder;
Note that in that sub-folder, I don’t need to include that webHttpBinding section because I’m going to configure this folder in IIS to force Basic authentication and HTTPS so there’s no “confusion” for WCF as to whether I want Basic auth or not.
Note that I also had to add a reference to System.ServiceModel.DomainServices.Hosting in order to be able to use the right service host factory here.
Note that I also had to add a reference to System.ServiceModel.DomainServices.Hosting in order to be able to use the right service host factory here.
No comments:
Post a Comment