Monday, March 16, 2015

OAuth2 Token Flow in Vanilla Javascript

Last time I’ve blogged on the OAuth2 authorization code flow. This time I am going to show how the other passive OAuth2 flow, namely the token flow, can be implemented in vanilla Javascript.

A side note: both the authorization code and the token flows are passive flows, i.e. they are designed for browser applications. They differ however in how and when the access token is acquired.

Authorization code flow Token flow
User navigates to an application page User navigates to an application page
Application server redirects the request to the authorization server Web browser’s Javascript redirects the request to the authotization server
User provides her credentials User provides her credentials
The authorization server redirects back to the application’s page and attaches the one time code to the response The authorization server redirects back to the application’s page and attaches the access token to the query string (in an uri fragment)
The application’s server calls a token service that exchanges the one time code for the access token The access token can be used from the client (Javascript) to access other services
The access token can be used from the server to access other services  

As you can see, in the authorization code flow the access token is available at the application’s server side, in the token flow the access token is available in the browser at the client-side.

To show how the token flow is implemented I need a simple wrapper for AJAX requests as the very last step of the flow will involve a call to a profile endpoint of the authorization server that will exchange the access token for basic user information (email, given name, surname, details vary depending on the actual OAuth2 provider).

/* Usage:
   ajax( {
      url : 'url'                        // required
      contentType : 'contentType'        // optional
      method : 'method'                  // required 
      headers : { h1 : 'v1', h2 : 'v2' } // optional
      data : ...                         // optional
      callback : function(response) { }  // required
      errback : function() { }           // optional
   } );
*/
function ajax(settings) {
    var req;
 
    if (!settings) return;
 
    if (XMLHttpRequest) {
        req = new XMLHttpRequest();
        if ('withCredentials' in req) {
            req.open(settings.method, settings.url, true);
            req.onerror = settings.errback;
            req.onreadystatechange = function () {
                if (req.readyState === 4) {
                    if (req.status >= 200 && req.status < 400) {
                        if (settings.callback) {
                            settings.callback(req.responseText);
                        }
                    } else {
                        if (settings.errback) {
                            settings.errback(new Error('Response returned with non-OK status'));
                        }
                    }
                }
            };
            if (settings.headers) {
                for (header in settings.headers)
                    req.setRequestHeader(header, settings.headers[header]);
            }
            if (settings.contentType)
                req.setRequestHeader('Content-Type', settings.contentType);
            req.send(settings.data);
        }
    }
}

The actual token flow is then as follows:

var server = {
    authorizationEndpoint: 
        "https://oauth2.server/oauth2/authorize?" +
        "response_type=token&" +
        "client_id=client_id&" + 
        "scope=whatever.scope.you.need&" + 
        "redirect_uri=http://application.server/Index.html",
    profileEndpoint: "https://oauth2.server/api/profile"
}
 
window.onload = function () {
 
    // the access token is passed back with the uri fragment
    // this little helper retrieves values from the uri fragment
    // http://stackoverflow.com/questions/5448545/how-to-retrieve-get-parameters-from-javascript
    var query = {};
 
    if (window.location.hash.indexOf("#") === 0) {
        window.location.hash.substr(1)
            .split("&")
            .forEach(
                function (item) {
                    query[item.split("=")[0]] = item.split("=")[1]
                });
    };
 
    if ( query.access_token == undefined ) {
        // unauthorized, redirect to the provider
        window.location.href = server.authorizationEndpoint;
    }
    else {
        // the token is here
        // use it to query the profile API
        var req = {
            url:      server.profileEndpoint,
            method:   'GET',
            callback: oauth2loggedin,
            headers: {
                Authorization : 'Bearer ' + query.access_token
            }
        };
 
        ajax(req);
    }
}
 
function oauth2loggedin(profilej) {
    var profile = JSON.parse(profilej);
    if (profile && profile.Email) {
        var user = document.getElementById('user');
        user.innerHTML = profile.Email;
    }
}

And that’s really it!

As you can see I attach a handler to the window’s load event. Here I check the uri fragment – if there is no access token, I assume this is the initial request and thus I redirect the request to the OAuth2 server.

If, however, the access token is here in the uri fragment, I can use it to create a request to the profile API passing the token in the authorization header (the format of the header is “Authorization : Bearer [token_here]”). Depending on the actual profile API, the returned object contains the information on the authenticated user.

Thursday, November 27, 2014

Simple OAuth2 Federated Authentication using DotNetOpenAuth

In my previous entry I’ve blogged on how to implement federated authentication to an external identity provider using the System.IdentityModel subsystem and the WS-Federation. This entry shows how to implement OAuth2 federated authentication (Google, Facebook) with the DotNetOpenAuth library.

Let’s start as usual – a simple web app with two pages, the Default page and the LoginPage. Add an authorization rule and forms authentication redirect to the LoginPage for requests that are not authenticated:

<authentication mode="Forms">
    <forms loginUrl="LoginPage.aspx" />
</authentication>
<authorization>
    <deny users="?" />
</authorization>

From the NuGet package manager, install the DotNetOpenAuth.Ultimate package (a standalone complete DotNetOpenAuth).

The passive OAuth2 (the authorization_code flow) requires the client_id and client_secret parameters (these two kind of authenticate the application at the identity provider side) as well as three Uris: the authentication uri (this is where the browse is redirected to authenticate  users), the token uri (this is where the one-time code returned from the login page is exchanged for an access_token) and the profile uri (which is a part of a graph API and allows the application to retrieve user profile information).

Let’s take Google for example. They have the OpenID Connect Discovery uri where a current information on authentication uris is published. This is where you get the information on all three required uris.

{
 "issuer": "accounts.google.com",
 "authorization_endpoint": "https://accounts.google.com/o/oauth2/auth",
 "token_endpoint": "https://accounts.google.com/o/oauth2/token",
 "userinfo_endpoint": "https://www.googleapis.com/plus/v1/people/me/openIdConnect",
 "revocation_endpoint": "https://accounts.google.com/o/oauth2/revoke",
 "jwks_uri": "https://www.googleapis.com/oauth2/v2/certs",
 "response_types_supported": [
  "code",
  "token",
  "id_token",
  "code token",
  "code id_token",
  "token id_token",
  "code token id_token",
  "none"
 ],
 "subject_types_supported": [
  "public"
 ],
 "id_token_alg_values_supported": [
  "RS256"
 ],
 "token_endpoint_auth_methods_supported": [
  "client_secret_post"
 ]
}

To get client_id and client_secret you need to register your application at the Google Console projects page. You need to create a new project, go to APIs&auth/Credentials, register a redirect uri (a uri in your app Google should redirect back to) and they generate both the client_id and client_secret.

Also remember to go to APIs&auth/APIs and switch Google+ API to ON (or your profile API calls will end up with 403)!

Be warned that some details of OAuth2 Google authentication have changed lately and are subjects for further changes.

When you are ready, go back to Visual Studio.

We are going to use the WebServerClient class which is designed to handle the OAuth2 authorization_code flow. We inherit from it to provide all Google endpoints:

public class GoogleClient : WebServerClient
{
    private static readonly AuthorizationServerDescription GoogleDescription =
        new AuthorizationServerDescription
        {
            TokenEndpoint         = new Uri( "https://accounts.google.com/o/oauth2/token" ),
            AuthorizationEndpoint = new Uri( "https://accounts.google.com/o/oauth2/auth" ),
            ProtocolVersion       = ProtocolVersion.V20,                
        };
 
    public const string ProfileEndpoint = "https://www.googleapis.com/plus/v1/people/me/openIdConnect";
 
    public const string OpenId       = "openid";
    public const string ProfileScope = "profile";
    public const string EmailScope   = "email";
 
    public GoogleClient()
        : base( GoogleDescription )
    {
    }
}

We also need a helper class to deserialize JSON profile information

public class GoogleProfileAPI
{
    public string email { get; set; }
    public string given_name { get; set; }
    public string family_name { get; set; }
 
    private static DataContractJsonSerializer jsonSerializer =
        new DataContractJsonSerializer( typeof( GoogleProfileAPI ) );
 
    public static GoogleProfileAPI Deserialize( Stream jsonStream )
    {
        try
        {
            if ( jsonStream == null )
            {
                throw new ArgumentNullException( "jsonStream" );
            }
 
            return (GoogleProfileAPI)jsonSerializer.ReadObject( jsonStream );
        }
        catch ( Exception ex )
        {
            return new GoogleProfileAPI();
        }
    }
}

and a technical helper class to strip off unnecessary query string parameters from the web API calls

public class MyAuthorizationTracker : IClientAuthorizationTracker
{
 
    public IAuthorizationState GetAuthorizationState( 
        Uri callbackUrl, 
        string clientState )
    {
        return new AuthorizationState
        {
            Callback = new Uri( callbackUrl.GetLeftPart( UriPartial.Path ) ) 
        };
    }
 
}

Actual OAuth2 flow code is straightforward now

protected void Page_Load( object sender, EventArgs e )
 {
     IAuthorizationState authorization = gClient.ProcessUserAuthorization();
     // Is this a response from the Identity Provider
     if ( authorization == null )
     {
         // no
         
         // Google will redirect back here
         Uri uri = new Uri( "http://localhost:62889/LoginPage.aspx" );
         // Kick off authorization request with OAuth2 scopes
         gClient.RequestUserAuthorization( returnTo: uri, 
             scope: new[] { GoogleClient.OpenId, GoogleClient.ProfileScope, GoogleClient.EmailScope } );
     }
     else
     {
         // yes
 
         var request = WebRequest.Create( GoogleClient.ProfileEndpoint );
         // add an OAuth2 authorization header
         // if you get 403 here, turn ON Google+ API on your app settings page
         request.Headers.Add( 
              HttpRequestHeader.Authorization, 
              string.Format( "Bearer {0}", Uri.EscapeDataString( authorization.AccessToken ) ) );
         // Go to the profile API
         using ( var response = request.GetResponse() )
         {
             using ( var responseStream = response.GetResponseStream() )
             {
                 var profile = GoogleProfileAPI.Deserialize( responseStream );
                 if ( profile != null &&
                     !string.IsNullOrEmpty( profile.email ) )
                     FormsAuthentication.RedirectFromLoginPage( profile.email, false );
             }
         }
     }
 }

The Google Client is just

// replace with actual values!
public readonly GoogleClient gClient = new GoogleClient
{
    AuthorizationTracker       = new MyAuthorizationTracker(),
    ClientIdentifier           = "my client id",
    ClientCredentialApplicator = ClientCredentialApplicator.PostParameter( "my client secret" )
};