DevelopMENTAL Madness

Thursday, July 30, 2009

Migrating away from Blogger.com

I’ve been underground since the beginning of the month and haven’t been working on any new posts. I have several new topics in the pipeline but I’ve been focusing on two projects that are keeping me away from my blog at the moment.

The first is digging into Silverlight. My employer has been getting requests for developers with Silverlight experience and so I’ve been working on digging in a bit more so I’m ready when I need it. I’m using my work on my project YouthSportsFan.com to give me something real-world to cut my teeth on and I’ll have some interesting posts that will come out of it. One is something I’ve already touched on lightly – design-time databinding. Also, I’ve been playing with custom templates and using them to improve the UI for a couple of the Silverlight Toolkit controls. I’m excited about both those upcoming posts.

The second thing I’ve been working on is indirectly related to my work on Silverlight. A colleague of mine has been very vocal (including bugging me in comments here on my blog) about getting me to switch away from Blogger.com. This got me thinking about how nice it would be to host running Silverlight samples in my blog posts instead of just the code snippets I usually post. So I’ve purchased my own domain developmentalmadness.com and it’s currently hosted on GoDaddy. I’ve downloaded Subtext as a blog engine and I’m customizing it at the moment.

My most important goal is that the links I’ve worked hard to get posted around the web will continue to work. I’ve put a lot of work into promoting my blog this year and I don’t want any of the links placed by myself or my readers to break. So far, Subtext has been brilliant in allowing me to tweak a few web.config settings which will allow me to respond to the requests I expect to be forwarded by Blogger.com when I switch to a custom domain. I’m excited about this and I will post a follow up when I’ve completed this so others can make the switch as well.

Wednesday, July 08, 2009

Google Chrome OS: Speculations Abound, What Do You Think?

I'm very curious to watch how the new Chrome OS will develop. I've been reading some of the buzz around it today and have even been reading some of the comments around it. I use Chrome (the web browser) everyday, it’s my main browser. As of this week I even got my wife to finally start using it after IE locked up her computer again and she decided it was the last time.

However, I doubt I will be ever using Chrome OS – or will I?

My prediction is that I am already using Chrome OS. If you follow the Chrome Dev Channel blog you’ve probably noticed that as the Google team is hard at work on v3 they are working on versions for Linux and Mac. They have been working on this since v2 was released back in May.

According to the Google blog Chrome OS will run on Linux and here are their stated goals:

  • run on both x86 as well as ARM chips
  • most of the user experience takes place on the web
  • Google Chrome running within a new windowing system on top of a Linux kernel
  • users don't have to deal with viruses, malware and security updates
  • data to be accessible to them wherever they are and not have to worry about losing their computer or forgetting to back up files
  • [users] don't want to spend hours configuring their computers to work with every new piece of hardware, or have to worry about constant software updates

These seemingly lofty goals are nothing new. For at least a year I’ve been hearing about software that allows you to bypass the OS and quickly boot straight to a “browser only mode”. Two examples are gOS and Presto. gOS is what I think Chrome OS will be like – the browser IS the OS. You boot directly into a web browser and your entire experience is inside the browser.

Applications

When Chrome was first released Google talked about how the browser was becoming (is?) its own platform for application development. They developed the Chrome JavaScript engine – V8 – to run JavaScript really fast to allow more to be done on the client. The idea is that if the JavaScript engine is “fast enough”, then it is adequate for most applications.

File Storage

Google Gears has been around for a while now and it means you don’t need local storage, other than a local cache. Gears allows you to make cross-domain requests using a “cross-origin worker”. This would allow your files to be stored in the cloud by any service provider (Flickr, Picasa, Windows Live) and then you can use any web application (doesn’t have to be the one provided by the aforementioned providers) to browse your photos as long as you grant it permission.

Video

The HTML 5 specification now makes video a first-class citizen of your web browser. Chrome supports playing video natively in the browser without needing a plug-in (pretty nice for Google since Chrome doesn’t support plug-ins). Again, you store your videos in the cloud and watch them anywhere.

Security

At least for now Chrome is the most secure web browser available. The security (simplified) is two tiered. The second tier being the sandbox. There are current exploits for Chrome, but those exploits land the attacker in the sandbox. This means that a hacker’s exploit must additionally exploit the sandbox. So a bug must expose both a problem with the browser’s rendering engine (Webkit) and expose a bug in the sandbox. So far, this has not been done. Could it be done? In theory, but until that happens there are no exploits for Chrome. Because of this, if Chrome were your entire OS then (speaking of today) you’re most likely you’re secure.

Applications

As a programmer, would I ever use Chrome OS? Right now, no. Developers require an IDE and debugging tools, source control integration and other such tools. All this requires software running locally on my desktop, but will it always be that way? Maybe not, just consider the Mozilla Labs Bespin project. At the moment it’s a far cry from Microsoft’s Visual Studio IDE, but do most developers really need all the tools provided by Visual Studio? I know I probably don’t use 10%. Statement completion, syntax highlighting and a compiler would be adequate. I’m not saying that’s all I use, other features make me much more productive but I’m saying it’s possible to provide many of the features in the cloud – even for an application developer.

For a graphics designer, you might think the browser might not be adequate either. But there are already services for the novice which allow you to edit photos, and with the addition of VML who’s to say that we’re so far from professional graphics programs hosted in the cloud either?

But for the average computer user, as time progresses people will become less objective of their files being located exclusively on the web. If Google can convince people that Google Docs has all the functionality they need along with other programs people use (photo/video editing) then it’s very conceivable that Chrome OS (and other similar OS’s) could be very successful.

Hardware Integration

So the only piece of this puzzle that isn’t immediately apparent to me at this point is how they will integrate with hardware devices. Maybe because I’m a web programmer and not an OS programmer. Google will need to allow you to get images off your camera, videos off your camcorder. Or at least read from a card reader. Then you need to upload those files to your preferred cloud provider. You’ll need to install devices like web cams and printers.

All this requires drivers and a way to access these devices. How do they plan on doing this without exposing vulnerabilities? Google has stated that if you want to develop applications for Chrome OS your platform is the web – HTML, JavaScript and any server side platform you choose. But the web is not currently allowed to access your devices (for good reason).

Conclusion

Chrome the web browser already does most of what Google has envisioned for their new OS. They just need to integrate it into a Linux distro (something they’ve already accomplished with Android) and provide an interface for managing/accessing hardware devices. I know that “just” is still a long stretch, but not that far away when you consider everything else they’ve already got done.

For now, I already have an operating system which loads in a matter of seconds - about 25, not much longer than it takes to fire up a web browser and load a web page. Windows 7 running on a SSD is pretty darn fast, I’ve written before about it. I've been running it for almost 2 months now and I still have free space on my drive and it’s showing no signs of slowing down. So I’ve got no need for an OS that only will load 10-15 seconds faster, but who’s to say.

Almost, 10 years ago, I said “hell no” when Ray Ozzy was talking about storing all our files on the web. But now I use Dropbox and Sky Drive. I’ve also used Google Docs a bit as well – but I don’t really create many docs or spreadsheets and for blogging Live Writer is better at keeping my formatting. So you could say I’ve bought into that philosophy over the years. And gradually I’m sure many others will too.

What do you think?

I really would like to know what you think. Am I dead wrong? Or dead on? How do you think they’ll integrate hardware? Do you think we’ll ever completely ditch the bloated operating systems of today?

Tuesday, July 07, 2009

Building a Single Sign On Provider Using ASP.NET and WCF: Part 4

This is the fourth and final article in a four part series on building a single sign on (SSO) provider using the ASP.NET platform. Make sure to check out part 1, part 2 and part 3.

Source Code

Implementing a Single Signon Provider

This is all a rehash since I’ve covered each point in detail to this point, but I’d like to tie everything together at this point and provide the source code. If you’d like detailed descriptions about how/why review the previous 3 parts. The full source code will be available here.

SSOFlowDiagram

  1. When an unauthenticated client requests a secured resource from the application that client is redirected to an authentication page.
  2. The authentication page makes a request (via JSONP) to the SSO service for a token which can then be presented to the application as evidence of the client’s identity with the SSO service.
  3. If the client has already authenticated with the SSO service and has an active session then skip to step #7 otherwise the request is denied.
  4. An unauthenticated client (SSO authentication) is redirected to a login page where the client then submits credentials for the SSO service.
  5. Upon submitting a valid set of credentials to the SSO service the client receives a cookie containing a token which is valid for the SSO service.
  6. Now that the client has successfully authenticated with the SSO service the client is redirected back to the application’s authentication page (step #2).
  7. The client receives an encrypted copy of the authentication ticket from the SSO service which it can then submit to the application. NOTE: This extra step is required when cookies are set to “HttpOnly = true” because they cannot be accessed via client script (javascript).
  8. The client now submits the SSO token to the application. The application verifies the token with the SSO service by forwarding it and asking if it is a valid token.
  9. The SSO service responds to the application with a flag indicating wither or not the submitted token is valid or not. Potentially, the SSO service could also provide additional information regarding the identity of the client. If the token was valid, the application then responds to the client with a token of it’s own which identifies the client to the application.
  10. The client, now authenticated with both the SSO service as well as the application, resubmits the request for the resource from step #1.

Service Implementation

We’re using the FormsAuthentication API within WCF to manage identity

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class SSOService : ISSOService, ISSOPartnerService
{
    #region ISSOService Members
 
    public SSOToken RequestToken()
    {
        SSOToken token = new SSOToken
        {
            Token = string.Empty,
            Status = "DENIED"
        };
 
        if (HttpContext.Current.Request.IsAuthenticated)
        {
            FormsIdentity identity = (FormsIdentity)HttpContext.Current.User.Identity;
 
            token.Token = FormsAuthentication.Encrypt(identity.Ticket);
            token.Status = "SUCCESS";
        }
 
        return token;
    }
 
    public bool Logout()
    {
        HttpContext.Current.Session.Clear();
        FormsAuthentication.SignOut();
        HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName);
        cookie.Expires = DateTime.Now.AddDays(-10000.0);
        HttpContext.Current.Response.Cookies.Add(cookie);
        return true;
    }
 
    public SSOToken Login(string username, string password)
    {
        SSOToken token = new SSOToken
        {
            Token = string.Empty,
            Status = "DENIED"
        };
 
        // authenticate user
        if (string.CompareOrdinal("foo", username) == 0
            && string.CompareOrdinal("bar", password) == 0)
        {
            Guid temp = Guid.NewGuid();
 
            DateTime issueDate = DateTime.Now;
            DateTime expireDate = issueDate.AddMonths(1);
 
            FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, username, issueDate, expireDate, true, temp.ToString());
            string protectedTicket = FormsAuthentication.Encrypt(ticket);
 
            HttpCookie authorizationCookie = new HttpCookie(FormsAuthentication.FormsCookieName, protectedTicket);
            authorizationCookie.Expires = expireDate;
            authorizationCookie.HttpOnly = true;
 
            HttpContext.Current.Response.Cookies.Add(authorizationCookie);
 
            token.Status = "SUCCESS";
            token.Token = protectedTicket;
        }
 
        return token;
    }
 
    public SSOUser ValidateToken(string token)
    {
        try
        {
            FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(token);
 
            return new SSOUser { 
                Username = ticket.Name, 
                SessionToken = new Guid(ticket.UserData) 
            };
        }
        catch
        {
            return new SSOUser { 
                Username = string.Empty, 
                SessionToken = Guid.Empty 
            };
        }
    }
 
    #endregion
}

Web Application Client

Web.Config – system.serviceModel definition

<system.serviceModel>
    <bindings>
        <webHttpBinding>
            <binding name="partnerBinding" >
            </binding>
        </webHttpBinding>
    </bindings>
    <behaviors>
        <endpointBehaviors>
            <behavior name="partnerEndpointBehavior">
                <webHttp/>
            </behavior>
        </endpointBehaviors>
    </behaviors>
    <client>
        <endpoint address="http://localhost:21259/SSOService.svc/partner" behaviorConfiguration="partnerEndpointBehavior"
                            binding="webHttpBinding" 
                            bindingConfiguration="partnerBinding"
                            contract="References.ISSOPartnerService" 
                            name="partnerEndpoint" />
    </client>
</system.serviceModel>

For the web application all that is required is to call the ValidateToken method of the SSO service and then provide the client with a token that identifies the client for the ASP.NET application (Authenticate method calls FormsAuth.SignIn()):

[AcceptVerbs(HttpVerbs.Post)]
public JsonResult Authenticate(string token, bool createPersistentCookie)
{
    SSOPartnerServiceClient client = new SSOPartnerServiceClient("partnerEndpoint");
    SSOUser user = client.ValidateToken(token);
 
    if (string.IsNullOrEmpty(user.Username)
        || Guid.Empty.Equals(user.SessionToken))
    {
        return Json(new { result = "DENIED" });
    }
 
    FormsAuth.SignIn(user, createPersistentCookie);
 
    return Json(new { result = "SUCCESS" });
}
 
public void SignIn(SSOUser user, bool createPersistentCookie)
{
    DateTime issueDate = DateTime.Now;
    FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, user.Username,
        issueDate, issueDate.AddMinutes(20), true, user.SessionToken.ToString());
 
    string protectedTicket = FormsAuthentication.Encrypt(ticket);
 
    HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, protectedTicket);
    cookie.HttpOnly = true;
    cookie.Expires = issueDate.AddMinutes(20);
 
    HttpContext.Current.Response.Cookies.Add(cookie);
}

jQuery Client

Authenticate.aspx View

$(function() {
    // get valid token from SSO
    $.get('http://localhost:21259/SSOService.svc/user/RequestToken?callback=?', {},
        function(ssodata) {
            var logonPage = '<%=Url.Action("LogOn", "Account") %>';
 
            if (ssodata.Status == 'SUCCESS') {
                // get target url
                var redirect = '<%=Request["redirectUrl"] %>';
                if (redirect == '')
                    redirect = '<%=Url.Action("Index", "Home") %>';
 
                // validate SSO token thru current application
                $.post('<%=Url.Action("Authenticate", "Account") %>',
                    { token: ssodata.Token, createPersistentCookie: true },
                        function(data) {
                            if (data.result == 'SUCCESS')
                                document.location = redirect;
                            else
                                document.location = logonPage;
                        }, 'json');
            } else {
                // not logged into SSO service, go to login page
                document.location = logonPage;
            }
        // make sure to specify JSONP
        }, 'jsonp');
});

Logon.aspx View

$(function() {
    $("#logon").click(function() {
        $("#error").text('').hide();
        $.get('http://localhost:21259/SSOService.svc/user/Login?callback=?',
            { username: $("#username").val(), password: $("#password").val() },
            function(ssodata) {
            if (ssodata.LoginResult.Status == 'DENIED') {
                $("#error").text('Login Failed').show();
            } else {
                document.location = '<%=Url.Action("Authenticate", "Account") %>';
            }
        }, 'jsonp');
    });
});

Conclusion

At this point you have everything you need to implement an SSO provider using ASP.NET. In theory, if you know how to setup WCF to communicate with other platforms other than the .NET Framework (something that is beyond the scope of this article) your SSO service can be used across platforms as well as domains.

If the scope of the applications you are targeting is smaller (they’re all part of the same domain or even on the same machine) there are certainly simpler ways to accomplish the same result with less effort. This is an example of a provider which can cover a group of applications from any domain and across any platform/hardware boundaries.

I’ve really learned a lot in this exercise, thanks for following me through this. I hope you enjoyed it as well.

Source Code

Labels: , , ,