Wednesday, April 22, 2015

No CAPTCHA reCAPTCHA for ASP.NET MVC

This time the implementation of the reCAPTCHA for ASP.NET MVC. We need an HTML helper:

/// <summary>
/// Extension dla recaptcha
/// </summary>
/// <remarks>
/// https://developers.google.com/recaptcha/docs/display
/// </remarks>
public static class CaptchaHtmlHelper
{
    // private and public keys are read from appsettings but
    // feel free to modify it so that is suits your needs
    private static string CaptchaPrivateKey
    {
        get
        {
            return ConfigurationManager.AppSettings["recaptchaPrivate"];
        }
    }
 
    private static string CaptchaPublicKey
    {
        get
        {
            return ConfigurationManager.AppSettings["recaptchaPublic"];
        }
    }
 
    public static MvcHtmlString GenerateCaptcha(
        this HtmlHelper htmlHelper )
    {
        TagBuilder script = new TagBuilder( "script" );
 
        script.MergeAttribute( "src", "https://www.google.com/recaptcha/api.js?hl=pl" );
        script.MergeAttribute( "async", "true" );
        script.MergeAttribute( "defer", "true" );
 
        TagBuilder div = new TagBuilder( "div" );
        div.MergeAttribute( "class", "g-recaptcha" );
        div.MergeAttribute( "data-sitekey", CaptchaPublicKey );
 
        return new MvcHtmlString( 
           script.ToString( TagRenderMode.Normal ) + 
           div.ToString( TagRenderMode.Normal ) );
    }
}

and an auxiliary validation attribute

/// <summary>
/// Captcha validation
/// </summary>
/// <remarks>
/// https://developers.google.com/recaptcha/docs/verify
/// </remarks>
public class CaptchaValidatorAttribute : ActionFilterAttribute
{
    public class APIResponse
    {
        public bool success { get; set; }
        public string[] error_codes { get; set; } 
    }
 
    private const string RESPONSE_FIELD_KEY = "g-recaptcha-response";
 
    public override void OnActionExecuting( ActionExecutingContext filterContext )
    {
        try
        {
            var query = HttpUtility.ParseQueryString( string.Empty );
            query["secret"] = ConfigurationManager.AppSettings["recaptchaPrivate"];
            query["response"] = filterContext.HttpContext.Request.Form[RESPONSE_FIELD_KEY];
 
            HttpClient client = new HttpClient();
            client.BaseAddress = new Uri( "https://www.google.com/" );
 
            // Add an Accept header for JSON format.
            client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue( "application/json" ) );
 
            HttpResponseMessage httpresponse = 
                client.GetAsync( string.Format( "recaptcha/api/siteverify?{0}", query.ToString() ) ).Result;
            var response = httpresponse.Content.ReadAsAsync<APIResponse>().Result;
 
            // this will push the result value into a parameter in our Action  
            filterContext.ActionParameters["captchaValid"] = response.success;
 
            base.OnActionExecuting( filterContext );
        }
        catch ( Exception ex )
        {
            filterContext.ActionParameters["captchaValid"] = false;                
        }
    }
}  

The validation attribute uses the HttpClient, make sure you reference the Microsoft.AspNet.WebApi.Client package.

To use the extension, place it in your view:

<div> 
    @Html.GenerateCaptcha()
</div>

and decorate your action with the validation attribute:

[HttpPost]
[CaptchaValidator]
public ActionResult TheActionName( bool captchaValid )
{
    if ( ModelState.IsValid && captchaValid )
    {
        ...
As you can see the validation attribute is responsible for setting the value of the captchaValid parameter and the value is then passed down to the action decorated with the attribute.

Monday, April 20, 2015

No CAPTCHA reCAPTCHA for ASP.NET WebForms

The code below is an example approach for reCAPTCHA implemented as ASP.NET WebForms control:

/// <summary>
/// NoCaptcha ReCaptcha for WebForms
/// </summary>
/// <remarks>
/// https://developers.google.com/recaptcha/docs/verify
/// </remarks>
public class NoRecaptchaControl : Control
{
    public class APIResponse
    {
        public bool success { get; set; }
        public string[] error_codes { get; set; }
    }
 
    public string PrivateKey { get; set; }
    public string PublicKey { get; set; }
    public string Theme { get; set; }
 
    private const string RESPONSE_FIELD_KEY = "g-recaptcha-response";
 
    public bool IsValid
    {
        get
        {
            try
            {
                var query         = HttpUtility.ParseQueryString( string.Empty );
                query["secret"]   = this.PrivateKey;
                query["response"] = this.Context.Request.Form[RESPONSE_FIELD_KEY];
 
                HttpClient client = new HttpClient();
                client.BaseAddress = new Uri( "https://www.google.com/" );
 
                // Add an Accept header for JSON format.
                client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue( "application/json" ) );
 
                HttpResponseMessage httpresponse = 
                   client.GetAsync( string.Format( "recaptcha/api/siteverify?{0}", query.ToString() ) ).Result;
                var response = httpresponse.Content.ReadAsAsync<APIResponse>().Result;
  
                return response.success;
            }
            catch ( Exception ex )
            {                
                return false;
            }
        }
    }
 
    protected override void Render( HtmlTextWriter writer )
    {
        writer.WriteBeginTag( "script" );
 
        writer.WriteAttribute( "src", "https://www.google.com/recaptcha/api.js" );
        writer.WriteAttribute( "async", "true" );
        writer.WriteAttribute( "defer", "true" );
        writer.Write( HtmlTextWriter.TagRightChar );
 
        writer.WriteEndTag( "script" );
 
        writer.WriteBeginTag( "div" );
        writer.WriteAttribute( "class", "g-recaptcha" );
        writer.WriteAttribute( "data-sitekey", this.PublicKey );
        writer.Write( HtmlTextWriter.TagRightChar );
 
        writer.WriteEndTag( "div" );
 
        base.Render( writer );
    }
}

This uses the HttpClient, make sure to reference the Microsoft.AspNet.WebApi.Client package.

The usage is simple, just put the control on your form:

<%@ Register TagPrefix="recaptcha" Namespace="Recaptcha" Assembly="My.Recaptcha.Assembly.Name" %>
 
...
 
<recaptcha:NoRecaptchaControl 
  ID="recaptcha" runat="server" 
  ClientIDMode="Static" Theme="clean" />

set both private and public keys:

protected override void OnPreInit( EventArgs e )
{
    base.OnPreInit( e );
 
    recaptcha.PrivateKey         = "...";
    recaptcha.PublicKey          = "...";
}

and you are done:

protected void button_Click( object sender, EventArgs e )
{
    if ( recaptcha.IsValid )
    {
        ...
    }
    else
    {
        ...
    }
}

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.