5

I need some help regarding mvc 5 using the google login provider and getting some youtube data. right now i think i get things a little mixed up. i'm not new to mvc but to version 5's owin middleware features. well, and not experienced in implementing oauth 2.0.

What i want:

  • Login to my MVC5 Application via Google.
  • Read some Youtube information from the logged in user.

What i have done so far:

  • Followed this Google OAuth 2.0 tutorial: Web applications (ASP.NET MVC).
    • Installed Google.Apis.Auth.MVC via NuGet.
    • Implemented AppFlowMetadata and AuthCallbackController as described.
    • Configured the redirect uri to "/AuthCallback/IndexAsync" as described.
  • Implemented a YoutubeController with the following action just to dump out some data:

    public async Task<ActionResult> IndexAsync()
    {
        var result =
            await new AuthorizationCodeMvcApp(this, new AppFlowMetadata())
            .AuthorizeAsync(cancellationToken);
    
        if (result.Credential == null)
        {
            return new RedirectResult(result.RedirectUri);
        }
        else
        {
            var service = new YouTubeService(new BaseClientService.Initializer
                {
                    HttpClientInitializer = result.Credential,
                    ApplicationName = "MyYoutubeApplication"
                });
    
            var playlists = service.Playlists.List("contentDetails, snippet");
            playlists.Mine = true;
    
            var list = await playlists.ExecuteAsync();
            var json = new JavaScriptSerializer().Serialize(list);
    
            ViewBag.Message = json; 
            return View();
        }
    }
    

So what this does, when trying to access /Youtube/IndexAsync is redirecting me to google, asking for my credentials. when entered, i'm asked if i'm ok with the permission asked by the application. after confirming, i get redirected to my page, showing my /Youtube/IndexAsync page with the requested data. so far so good, but that's not quite what i want.

what (i think) i have done here is that i completely bypassed the asp.net identity system. the user is not logged in to my application let alone registered.

i want the user to log in with google, register in my application and provide access to his youtube data. then, when on a specific page, retrieve data from the user's youtube account.

What i also have tried:

  • Following this ASP.Net MVC5 Tutorial
    • This tutorial does not mention the NuGet package "Google.Apis.Auth.MVC" and talks something about a magic "/signin-google" redirect uri".
    • This also works, but breaks the solution above, complaining about a wrong redirect uri.
    • When using this approach, it seems not right to me call AuthorizeAsync in YoutubeController again, since i should already be authorized.

So i'm looking for some light in the dark, telling me what i'm mixing all together :) I hope the question is not as confused as i am right now.

3
  • got the same issue too, did you to fix it? Commented Oct 27, 2014 at 21:51
  • nope, sorry. but still interested in an answer :) Commented Oct 30, 2014 at 13:10
  • @infadelic I'm facifacing the same problem, did you get any solution? Commented Nov 4, 2014 at 15:44

1 Answer 1

3

I managed to do this using GooglePlus, haven't tried Google. Here's what I did:

Install the nugets:

> Install-Package Owin.Security.Providers
> Install-Package Google.Apis.Youtube.v3

Add this to Startup.auth.cs:

var g = new GooglePlusAuthenticationOptions();
g.ClientId = Constants.GoogleClientId;
g.ClientSecret = Constants.GoogleClientSecret;
g.RequestOfflineAccess = true;  // for refresh token
g.Provider = new GooglePlusAuthenticationProvider
{
    OnAuthenticated = context =>
    {
        context.Identity.AddClaim(new Claim(Constants.GoogleAccessToken, context.AccessToken));
        if (!String.IsNullOrEmpty(context.RefreshToken))
        {
            context.Identity.AddClaim(new Claim(Constants.GoogleRefreshToken, context.RefreshToken));
        }
        return Task.FromResult<object>(null);
    }
};

g.Scope.Add(Google.Apis.YouTube.v3.YouTubeService.Scope.YoutubeReadonly);
g.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie;
app.UseGooglePlusAuthentication(g);

The above code does two things:

  1. Enable authentication via. Google+
  2. Requests for the access token and the refresh token. The tokens are then added as a claim in the GooglePlus middleware.

Create a method that will store the claims containing the token to the database. I have this in the AccountController.cs file

private async Task StoreGooglePlusAuthToken(ApplicationUser user)
{
    var claimsIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
    if (claimsIdentity != null)
    {
        // Retrieve the existing claims for the user and add the google plus access token
        var currentClaims = await UserManager.GetClaimsAsync(user.Id);
        var ci = claimsIdentity.FindAll(Constants.GoogleAccessToken);
        if (ci != null && ci.Count() != 0)
        {
            var accessToken = ci.First();
            if (currentClaims.Count() <= 0)
            {
                await UserManager.AddClaimAsync(user.Id, accessToken);
            }
        }

        ci = claimsIdentity.FindAll(Constants.GoogleRefreshToken);
        if (ci != null && ci.Count() != 0)
        {
            var refreshToken = ci.First();
            if (currentClaims.Count() <= 1)
            {
                await UserManager.AddClaimAsync(user.Id, refreshToken);
            }
        }
    }

You'll need to call it in 2 places in the AccountController.cs: Once in ExternalLoginCallback:

case SignInStatus.Success:
var currentUser = await UserManager.FindAsync(loginInfo.Login);
if (currentUser != null)
{
    await StoreGooglePlusAuthToken(currentUser);
}
return RedirectToLocal(returnUrl);

and once in ExternalLoginConfirmation:

var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user);
if (result.Succeeded)
{
    result = await UserManager.AddLoginAsync(user.Id, info.Login);
    if (result.Succeeded)
    {
        await StoreGooglePlusAuthToken(user);
        await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
        return RedirectToLocal(returnUrl);
    }
}

Now that we've got the users access token and refresh token we can use this to authenticate the user.

I tried a simple search I saw in the examples and it worked:

private async Task<Models.YouTubeViewModel> Search(string searchTerm)
{
    var user = (ClaimsPrincipal)Thread.CurrentPrincipal;

    var at = user.Claims.FirstOrDefault(x => x.Type == Constants.GoogleAccessToken);
    var rt = user.Claims.FirstOrDefault(x => x.Type == Constants.GoogleRefreshToken);

    if (at == null || rt == null)
        throw new HttpUnhandledException("Access / Refresh Token missing");

    TokenResponse token = new TokenResponse
    {
        AccessToken = at.Value,
        RefreshToken = rt.Value
    };

    var cred = new UserCredential(new GoogleAuthorizationCodeFlow(
                new GoogleAuthorizationCodeFlow.Initializer()
                {
                    ClientSecrets = new ClientSecrets()
                                    {
                                        ClientId = Constants.GoogleClientId,
                                        ClientSecret = Constants.GoogleClientSecret
                                    }
                }
            ),
            User.Identity.GetApplicationUser().UserName,
            token
        );

    var youtubeService = new YouTubeService(new BaseClientService.Initializer()
    {
        ApplicationName = this.GetType().ToString(),
        HttpClientInitializer = cred,
    });

    var searchListRequest = youtubeService.Search.List("snippet");
    searchListRequest.Q = searchTerm;
    searchListRequest.MaxResults = 50;

    // Call the search.list method to retrieve results matching the specified query term.
    var searchListResponse = await searchListRequest.ExecuteAsync();

    Models.YouTubeViewModel vm = new Models.YouTubeViewModel(searchTerm);
    foreach (var searchResult in searchListResponse.Items)
    {
        switch (searchResult.Id.Kind)
        {
            case "youtube#video":
                vm.Videos.Add(new Models.Result(searchResult.Snippet.Title, searchResult.Id.VideoId));
                break;

            case "youtube#channel":
                vm.Channels.Add(new Models.Result(searchResult.Snippet.Title, searchResult.Id.ChannelId));
                break;

            case "youtube#playlist":
                vm.Playlists.Add(new Models.Result(searchResult.Snippet.Title, searchResult.Id.PlaylistId));
                break;
        }
    }

    return vm;
}

Model Classes

public class Result 
{
    public string Title { get; set; }
    public string Id { get; set; }

    public Result() { }

    public Result(string title, string id)
    {
        this.Title = title;
        this.Id = id;
    }
}

public class YouTubeViewModel
{
    public string SearchTerm { get; set; }

    public List<Result> Videos { get; set; }
    public List<Result> Playlists { get; set; }
    public List<Result> Channels { get; set; }

    public YouTubeViewModel()
    {
        Videos = new List<Result>();
        Playlists = new List<Result>();
        Channels = new List<Result>();
    }

    public YouTubeViewModel(string searchTerm)
        :this()
    {
        SearchTerm = searchTerm;
    }
}

Reference: http://blogs.msdn.com/b/webdev/archive/2013/10/16/get-more-information-from-social-providers-used-in-the-vs-2013-project-templates.aspx

Sign up to request clarification or add additional context in comments.

2 Comments

What are Constants.GoogleAccessToken and Constants.GoogleRefreshToken?
@ebramtharwat they are just strings. GoogleAccessToken is urn:tokens:googleplus:accesstoken and GoogleRefreshToken is urn:tokens:googleplus:refreshtoken. Shouldn't make any difference though.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.