Secure Gmail Authentication with X-OAUTH2 SASL

In July 2014 Google has introduced the “More Secure Gmail Authentication feature”.
When Access for less secure apps is set to disabled then Google does not allow login over SASL PLAIN anymore (even on TLS).

During authentication Google replies with the not-authorized error. And they will notify you by email that access to an insecure application was denied.

CLIENT: <auth mechanism="PLAIN" xmlns="urn:ietf:params:xml:ns:xmpp-sasl">AUTH DATA</auth>
SERVER: <failure xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
  <not-authorized />
</failure>

When access for less secure apps is disabled you have to login with the X-OAUTH2 SASL mechanism the Gmail XMPP server offers. Here is a small tutorial how to achieve this with MatriX.

1) API Project

In order to access the Google API you have to setup a project in the Google API Console which provides you the Access credentials. Once you have created your API project you can request your credentials which include the Client Id and Client Secret. During the creation process of the app project we selected “native application”.

2) Request an Access Token

To authenticate with OAuth 2.0 we need to get our Access Token. The OAuth 2.0 is documented at the Google pages under the following 2 links:

There are also several .NET libraries available, e.g DotNetOpenAuth or Google APIs Client Library.

We are not going to use a third party library for now, because this help us also to understand the whole XAuth authentication flow.

We create a new form and put a Webbrowser control on it. When the form loads we have ro navigate to a Url which includes;

  • Client Id of our API project
  • A redirect Uri, we use this urn:ietf:wg:oauth:2.0:oob
  • scopes:
    • https://www.googleapis.com/auth/googletalk
      (required for XMPP authentication)
    • https://www.googleapis.com/auth/userinfo.email
      (optional, we can use this t get the username/email address of the user)

When our form with the webbrowser loads we navigate to this Url with teh following code:

const string CLIENT_SECRET  = "XXXXXXXYYYYYYYYYYYYZZZZZZZZZ";
const string CLIENT_ID      = "yyyyyyyyyyyyy-xxxxxxxxxxxxxxxxx.apps.googleusercontent.com";

private static Uri GetAutenticationUri()
{
    var postData = new List<string>
    {
        "client_id=" + CLIENT_ID,
        "redirect_uri=urn:ietf:wg:oauth:2.0:oob",
        "scope=https://www.googleapis.com/auth/googletalk https://www.googleapis.com/auth/userinfo.email",
        "response_type=code"
    };

    return new Uri("https://accounts.google.com/o/oauth2/auth" + "?" + string.Join("&", postData.ToArray()));
}


private void Form_Load(object sender, EventArgs e)
{
  // navigate to the Uri when the form loads
  webBrowser1.Url = GetAutenticationUri();
}

When you run the code you should see the following: google_xoauth2

3) Authentication

When the user now clicks on “Accept” in the authentication dialog the page in the browser reloads. The new page contains an Authentication code in the title tag. The authentication code is also in the body of the result page. We have seen cases where the code in the title tag was cut off. So it could be more secure to parse it from the body.

private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
    string docTitle = ((WebBrowser) sender).DocumentTitle.ToLower();
    // when there is "success code=" in the title its the page we are looking for.
    if (docTitle.IndexOf("success code=") > -1)
    {
        webBrowser1.Visible = false; // Hide the webbrowser
        // we can find the auth code in the title tag of the html page
        string authCode = webBrowser1.DocumentTitle.Replace("Success code=", "");
        
        ProcessAuthorizationCode(authCode);
    }
    else if (docTitle.IndexOf("denied error=") > -1)
    { 
        // user denied the app dialog, handle this here.        
    }
}

4) Exchanging the Authentication Code

Now we have the Authentication code, and can use it to request the access token and refresh token over a simple HTTP Post. The result is in JSON, we use Newtonsoft.Json to process it.

public void ProcessAuthorizationCode(string authCode)
{
    var postData = new List<string>
    {
        "code=" + authCode,
        "client_id=" + CLIENT_ID,
        "client_secret=" + CLIENT_SECRET,
        "redirect_uri=urn:ietf:wg:oauth:2.0:oob",
        "grant_type=authorization_code"
    };

    //Posting the Data to Google
    string responseString = Post("https://accounts.google.com/o/oauth2/token", postData);

    //Processing the JSON Response
    JObject jObject = JObject.Parse(responseString);
    
    JToken refreshToken = jObject.SelectToken("refresh_token");
    // Checking for errors.
    if (refreshToken == null)
    {
        //this._ErrorMessage = response;
    }
    else
    {
        RefreshToken = refreshToken.ToString();
        AccessToken = jObject.SelectToken("access_token").ToString();
        // close the browser Windows, we are done
        Close();
    }
}

public static string Post(string url, List<string> postData)
{
    var request = WebRequest.Create(url);

    // Preparing the post data
    var toPost = string.Join("&", postData.ToArray());
    byte[] byteArray = Encoding.UTF8.GetBytes(toPost);

    request.Method = "POST";
    request.ContentType = "application/x-www-form-urlencoded";
    request.ContentLength = byteArray.Length;
    var dataStream = request.GetRequestStream();
    
    dataStream.Write(byteArray, 0, byteArray.Length);
    dataStream.Close();
    try
    {
        // read the response
        var response = request.GetResponse();
        dataStream = response.GetResponseStream();
        var reader = new StreamReader(dataStream);
        string responseString = reader.ReadToEnd();
        
        reader.Close();
        dataStream.Close();
        response.Close();
        
        return responseString;
    }
    catch (Exception)
    {
        return null;
    }
}

5) Getting the username (optional)

The username for XMPP authentication for XOAUTH2 SASL is the users email address. You can either ask the user to enter it in your software. Or you can use your access token to request more user details. This is why we have added the scope https://www.googleapis.com/auth/userinfo.email. This allows us to get the username with a post to the following url:
https://www.googleapis.com/oauth2/v1/userinfo?access_token={your_access_token}

This request returns JSON payload where you could fine the users email address.

6) Authenticate with MartiX

Now we have everything to perform the XOAUTH2 authentication with MatriX. We need to subscribe to the OnBeforeSasl event to manual select the SASL mechanism and pass our access token.

private void xmppClient_OnBeforeSasl(object sender, Matrix.Xmpp.Sasl.SaslEventArgs e)
{
  // make sure we use the correct username for authentication    
  xmppClient.Username = "user12345@gmail.com";
  // disable automatic SASL algrithm selection in MatriX
  e.Auto = false;
  // manual specify the X_OAUTH2 SASL mechanism 
  e.SaslMechanism = Matrix.Xmpp.Sasl.SaslMechanism.X_OAUTH2;
  
  //pass the Access token to MatriX  
  e.SaslProperties = new OAuthProperties()
  {
      AccessToken = "AccessToken"
  };    
}

Leave a Reply

Your email address will not be published. Required fields are marked *