A Youtube Connector for Sitecore Media Framework Part 4 – Google Authentication

In this blog post, I will talk about how I am authenticating to Google service via OAuth2 protocol to access my Youtube account from Sitecore. To use any service in Google, first I need to create a project in the Google Developer Console https://console.developers.google.com/project

Sitecore Media Framework

Once the project is created, I enabled the Youtube Data API V3 and Youtube Analytics API for this new project.

Sitecore Media Framework

Next, I need to create the credentials. There are two types of credentials for accessing Google service, OAuth2 and Public API Access. The OAuth credential allows user to share their Google data with any application. In my case the application is Sitecore. When Youtube Connector for Sitecore tries to import user’s video data from Youtube, it will open up a browser window for the user to log on using the Google account and give permission to access user’s Youtube channel. The Public API Access doesn’t require any interaction from the user and it is not as secured as OAuth2. I will be using OAuth2 authentication. Let’s create an OAuth2 credentials. When I click on the ‘Create New Client ID’ button, it presents me the dialog below to create the Client ID. My application type is ‘Web Application’ and the Authorize Redirect Uri is http://myyoutubevideos.local.com/sitecore/shell/Applications/GoogleOAuth/Authenticate.aspx

Sitecore Media Framework

 

Once the Client ID is created, Google provides a Client Id and Client Secret like below. I created this for this blog and deleted them. This cannot be used to access my Youtube account.

Sitecore Media Framework

I store the Client Id and the Client Secret in the ‘MyYoutubeVideos Account’ item fields and they are used for the authentication.

Sitecore Media Framework

Now, when the user clicks on the ‘Import All’ button in the ‘Media Framework’ tab of the Content Editor, first thing I need to check if the user is already authenticated with Google Service. If not, take the user through the authentication process. This starts with association of a type with the command in the config file. I created a config file for the connector Sitecore.MediaFramework.Services.Youtube.config and here is the configuration that creates this association.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
 <sitecore>
 <commands>
 <command name="mediaFramework:ManualImport:Youtube" type="Sitecore.MediaFramework.Youtube.Commands.YoutubeImportContent,Sitecore.MediaFramework.Youtube"/>
 </commands>
 </sitecore>
</configuration>

Basically, the following Execute method in the YoutubeImportContent class gets called when button is clicked.

namespace Sitecore.MediaFramework.Youtube.Commands
{
  public class YoutubeImportContent : ImportContent
  {
    public override void Execute(CommandContext context)
    {
      Assert.ArgumentNotNull((object) context, "CommandContext");
      Assert.ArgumentNotNull((object)context.Items[0], "CommandContext");
      var accountItem = context.Items[0];
      var youtubeService=new YoutubeAuthenticator(accountItem).Authenticate();
      base.Execute(context);
    }
  }
}

I had to derive YoutubeImportContent class from MediaFramework’s ImportContent class. In the original design Sitecore expects that the implementer of the Media Framework will always associate Sitecore.MediaFramework.Commands.ImportContent class to the Import command button. This works as long as I can authenticate user without having them logging on to his/her account via some UI. When there is a UI involved, this doesn’t work as ImportContent class starts a job in the background and cannot prompt the user for some input. At least not in a conventional way. So, I need to finish OAuth2 authentication before I hand over the control to ImportContent class. This design also created some difficulty for me as I cannot easily share data between objects, because the program switches from UI thread to the background thread after authentication.

Execute method in YoutubeImportContent class makes call to Authenticate method in the YoutubeAuthenticator class. This method returns a Google.Apis.YouTube.v3.YoutubeService object, if the user is already authenticated. If not, it uses  SheerResponse.Eval method (highlighted below) to open a browser window and redirect the user to /sitecore/shell/Applications/GoogleOAuth/Authenticate.aspx page of my Sitecore.SharedSource.GoogleOAuth asp.net application where all OAuth2 magic happens.


namespace Sitecore.MediaFramework.Youtube.Security
{
  public class YoutubeAuthenticator
  {
    public static YouTubeService YoutubeService = null;
    private const string AuthUrl = "/sitecore/shell/Applications/GoogleOAuth/Authenticate.aspx";
    private const string UserId = "userId";

    public string ClientId { get; set; }
    public string ClientSecret { get; set; }

    public YoutubeAuthenticator(Item accountItem)
    {
      ClientId = accountItem.Fields["ClientId"].Value;
      ClientSecret = accountItem.Fields["ClientSecret"].Value;
    }

    public YouTubeService Authenticate()
    {
      var dataFolder = Configuration.Settings.DataFolder;
      var fileDataStoreLocation = dataFolder + @"\YouTubeService.api.auth.store";
       var flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
       {
         ClientSecrets = new ClientSecrets
        {
          ClientId = ClientId,
          ClientSecret = ClientSecret
        },
          Scopes = new[] { YouTubeService.Scope.YoutubeReadonly },
          DataStore = new FileDataStore(fileDataStoreLocation)
       });

      var uri = HttpContext.Current.Request.Url.Scheme + "://" + HttpContext.Current.Request.Url.Authority + AuthUrl;
      var result = new AuthorizationCodeWebApp(flow, uri, uri).AuthorizeAsync(UserId,
 CancellationToken.None).Result;
      if (result.Credential == null)
      {
        var authUrlWithCredential = string.Format("{0}?ClientId={1}&ClientSecret={2}", AuthUrl, ClientId,
 ClientSecret);
        SheerResponse.Eval("window.open('" + authUrlWithCredential + "', '_blank', height='400', width='400')");
        return null;
      }
      else
      {
        YoutubeService= new YouTubeService(new BaseClientService.Initializer
          {
            ApplicationName = "Sitecore Media Framework Connector for Youtube.",
            HttpClientInitializer = result.Credential
          });
        return YoutubeService;
      }
   }
 }
}

The Sitecore.SharedSource.GoogleOAuth is a separate web project and it can be used with any application requires to use OAuth2 authentication for the Google service. Following code initiate the OAuth2 authentication for the user. The /sitecore/shell/Applications/GoogleOAuth/Authenticate.aspx page with the hostname is configured as the Authorized Redirecct Uri in the Google project.


namespace Sitecore.SharedSource.GoogleOAuth
{
  public partial class Authenticate : System.Web.UI.Page
  {
    private const string AuthUrl = "/sitecore/shell/Applications/GoogleOAuth/Authenticate.aspx";
    private static string _clientId = string.Empty;
    private static string _clientSecret = string.Empty;

    protected void Page_Load(object sender, EventArgs e)
    {
      var dataFolder = Configuration.Settings.DataFolder;
      if (_clientId.Equals(string.Empty))
      {
        _clientId = Request.QueryString["ClientId"];
        _clientSecret = Request.QueryString["ClientSecret"];
      }

      GoogleAuthorizationCodeFlow flow =
        new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
        {
          ClientSecrets = new ClientSecrets
          {
            ClientId = _clientId,
            ClientSecret = _clientSecret
          },
          Scopes = new[] { YouTubeService.Scope.YoutubeReadonly },
          DataStore = new FileDataStore(dataFolder + "\\YouTubeService.api.auth.store")
        });
     var userId = "userId";

     var code = Request["code"];
     if (code != null)
     {
       var uri = Request.Url.ToString();

       var token = flow.ExchangeCodeForTokenAsync(userId, code,
uri.Substring(0, uri.IndexOf("?")), CancellationToken.None).Result;

       // Extract the right state.
       var oauthState = AuthWebUtility.ExtracRedirectFromState(
flow.DataStore, userId, Request["state"]).Result;
       Response.Redirect(oauthState);
     }
     else
     {
       var uri = HttpContext.Current.Request.Url.Scheme + "://" + HttpContext.Current.Request.Url.Authority + AuthUrl;
       var result = new AuthorizationCodeWebApp(flow, uri, uri).AuthorizeAsync(userId,CancellationToken.None).Result;
       if (result.RedirectUri != null)
       {
         // Redirect the user to the authorization server.
         Response.Redirect(result.RedirectUri);
       }
      }
  }
}

The above code checks if the user’s Google authentication token exists in the machine. If not, and if the user is not already logged into Google, the code redirects the user to the Google account login screen.

Sitecore Media Framework

Once the user logs in, Google shows the following screen to the user to give the permission to the application, so that, the application can access the user’s Youtube channel.

 

Sitecore Media Framework

After the user accepts the request, the application shows the authentication confirmation message. The application skips the above steps if the Google authentication token already exists in the machine. If the token has expired, it gets a new token and shows the authentication confirmation message.

Sitecore Media Framework

That’s all for the Google Authentication from my Youtube connector. In next blog, I will show how to read the data from Youtube and create the media items in Sitecore.

Advertisements

About Himadri Chakrabarti

I am a software developer architect and a Sitecore MVP. My professional interest is everything and anything related to Software Architecture, .NET, Sitecore, Node.js, NoSQL etc. Outside of my profession, I am a hobbyist photographer. Link to my photography site http://himadriphotography.com/
This entry was posted in Media Framework, Sitecore and tagged , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s