Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Google.Apis.Auth.OAuth for Webforms

Does anyone have a simple example how to use Google.Apis.Auth.OAuth with C# Webforms? I can only find the MVC ones.

like image 295
user974332 Avatar asked Apr 07 '14 11:04

user974332


2 Answers

I struggled for a while converting an original sample for a console application - it is really intended to show how your app can access the users' own google sheets.

https://developers.google.com/sheets/api/quickstart/dotnet

There is an MVC based example - which doesn't lend itself very well to webforms - this however is again really for when the website is accessing any user's data

https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#web-applications-aspnet-mvc

So part of the process for both of these - is that you get sent to Google's API to grant access to the user's data e.g. access to the Google sheet that you own. After granting access it goes back to the return url you specify.

The console example opens a browser for the granting access bit.

Once you have granted access a file is saved which stores this permission and it doesn't need to ask for it again.

I converted the console app code to run in a webform but this required that I copy the credential file (that I had generated using the console app sample). It is not possible to generate the credential file as it tries to get IIS to launch a browser to get authority which the IIS user does not have permission to do (and wouldn't work in production anyway). If you have copied the file to somewhere IIS can view it it will work though (if the credentials file exists it skips opening a browser to get permission).

Here is my sample for this (code behind for webform):

Imports Google.Apis.Auth.OAuth2
Imports Google.Apis.Sheets.v4
Imports Google.Apis.Sheets.v4.Data
Imports Google.Apis.Services
Imports Google.Apis.Util.Store
Imports System.Collections.Generic
Imports System.Threading

Private Sub btnImportData_Click(sender As Object, e As EventArgs) Handles btnImportData.Click

    Dim ApplicationName As String = "Google Sheets API .NET Quickstart"
    'Dim credPath As String = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal)
    'credPath = Path.Combine(credPath, ".credentials/sheets.googleapis.com-dotnet-quickstart.json")

    Dim credPath As String = Server.MapPath("~/YourPathToCredentials/google-api-credentials")

    Dim CS As New ClientSecrets With {
            .ClientId = "-- YOUR CLIENT ID --",
            .ClientSecret = "-- YOUR CLIENT SECRET --"
        }
    'Dim Scopes As String() = {SheetsService.Scope.SpreadsheetsReadonly}
    Dim Scopes As IEnumerable(Of String) = {SheetsService.Scope.Drive}
    'Dim DataStore = New FileDataStore("Drive.Api.Auth.Store")
    Dim credential As UserCredential = GoogleWebAuthorizationBroker.AuthorizeAsync(CS, Scopes, "user", CancellationToken.None, New FileDataStore(credPath, True)).Result
    Diagnostics.Debug.WriteLine("Credential file saved to: " & credPath)

    Dim service = New SheetsService(New BaseClientService.Initializer() With {
        .HttpClientInitializer = credential,
        .ApplicationName = ApplicationName
    })
    'Glossary Spreadsheet used as data repository for Glossary BETA Google Forms prototype
    Dim spreadsheetId As String = "-- YOUR GOOGLE SHEET ID --"
    Dim range As String = "Form responses 1!A2:G"
    Dim request As SpreadsheetsResource.ValuesResource.GetRequest = service.Spreadsheets.Values.[Get](spreadsheetId, range)
    Dim response As ValueRange = request.Execute()
    Dim values As IList(Of IList(Of Object)) = response.Values

    If values IsNot Nothing AndAlso values.Count > 0 Then
        Diagnostics.Debug.WriteLine("Category, Term, Definition")

        For Each row In values
            Diagnostics.Debug.WriteLine("{0}, {1}, {2}", row(3), row(1), row(2))
        Next
    Else
        Diagnostics.Debug.WriteLine("No data found.")
    End If
End Sub

A far more relevant and easier approach is a completely different type which is designed for server-to-server communications i.e. webform code-behind to Google API.

For this you need a "service account" - these credentials are created on the api backend and can then be used by a web application in code - which was exactly the use case that I was looking for (always the same data store owned by me in Google Sheets).

https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#service-account

https://developers.google.com/identity/protocols/OAuth2ServiceAccount

So here is my example using a Google Service account.

Note - I created a Service Account on the Google API Console - this generates an email address for this back-office account - I had to give permission to this email for the sheet I was interested in. The code examples on Google were for the older P12 format - whereas the API console recommends the newer JSON format. My example code has been modified to use the more modern JSON format. When you create the service account it gives you a file to download make sure you save a copy somewhere safely as you will not be able to regenerate it. Its not the end of the world if you do but you won't be able to use that account again you will have to make a new one and give that one permissions.

Imports System.Data
Imports Google.Apis.Auth.OAuth2
Imports Google.Apis.Sheets.v4
Imports Google.Apis.Sheets.v4.Data
Imports Google.Apis.Services
Imports Google.Apis.Util.Store
Imports System.Collections.Generic
Imports System.Threading
Imports System.IO

    Public Sub ImportGlossaryGoogleSheetData_ServiceAccount()
        Diagnostics.Debug.WriteLine("=====================================")
        Diagnostics.Debug.WriteLine("Import Glossary using Service Account")
        Diagnostics.Debug.WriteLine("=====================================")
        Dim credPath As String = Server.MapPath("~/YourPathToCredentials/google-api-credentials/credentialfile.json")
        Dim credential As ServiceAccountCredential
        Using stream As New FileStream(credPath, FileMode.Open, FileAccess.Read, FileShare.Read)
            credential = GoogleCredential.FromStream(stream).CreateScoped({SheetsService.Scope.Drive}).UnderlyingCredential
        End Using
        Dim service = New SheetsService(New BaseClientService.Initializer() With {
            .HttpClientInitializer = credential,
            .ApplicationName = "YOUR APP NAME"
        })
        'Glossary Spreadsheet used as data repository for Glossary BETA Google Forms prototype
        Dim spreadsheetId As String = "-- YOUR GOOGLE SHEET ID --"
        Dim range As String = "Form responses 1!A2:G"
        Dim request As SpreadsheetsResource.ValuesResource.GetRequest = service.Spreadsheets.Values.[Get](spreadsheetId, range)
        Dim response As ValueRange = request.Execute()
        Dim values As IList(Of IList(Of Object)) = response.Values
        If values IsNot Nothing AndAlso values.Count > 0 Then
            Diagnostics.Debug.WriteLine("Category, Term, Definition")

            For Each row In values
                Diagnostics.Debug.WriteLine("{0}, {1}, {2}", row(3), row(1), row(2))
            Next
        Else
            Diagnostics.Debug.WriteLine("No data found.")
        End If
    End Sub

I hope this explains what I discovered about the different approaches given by google:

  • OAuth - a generic approach to allow your code permission to view the user's data e.g. Your website looking at data in the user's Google Sheet.

  • Service Account - server to server access to Google API - e.g. your website looking at specific data using Google API i.e. hard coded to look at data that you own and generate credentials for in Google's API backend.

like image 67
DominicTurner Avatar answered Nov 09 '22 01:11

DominicTurner


It's been a while but I thought this might be useful for someone:

GoogleFlowMetaData:

public class GoogleFlowMetaData
{
    private static readonly IAuthorizationCodeFlow flow =
    new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
    {
        ClientSecrets = new ClientSecrets
        {
            ClientId = "your client ID",
            ClientSecret = "your client secret"
        },
        Scopes = new[] { CalendarService.Scope.Drive/*or any service you want*/ },
        DataStore = new FileDataStore("Drive.Api.Auth.Store")
    });

    public IAuthorizationCodeFlow Flow
    {
        get { return flow; }
    }
}

AuthorizationCodeApp:

public class AuthorizationCodeApp : AuthorizationCodeWebApp
{
    private readonly GoogleFlowMetaData flowData;
    private readonly string redirectUri;
    private readonly string state;
    private readonly string userID;
    public GoogleFlowMetaData FlowData { get { return flowData; } }
    public AuthorizationCodeApp(GoogleFlowMetaData flowData, string redirectUri, string state, string userID):base(
        flowData.Flow,redirectUri,state)
    {
        this.redirectUri = redirectUri;
        this.state = state;
        this.userID = userID;
    }
    public Task<AuthResult> AuthorizeAsync(CancellationToken taskCancellationToken)
    {
        return base.AuthorizeAsync(userID, taskCancellationToken);
    }
}

now to run an Async task wherever you may want to define something like:

 public async Task<string> IndexAsync(CancellationToken cancellationToken)
    {
        var result = await new AuthorizationCodeApp(new GoogleFlowMetaData(), "http://localhost:4356/API/GAuth","","beebee").
            AuthorizeAsync(cancellationToken);

        if (result.Credential != null)
        {
           var service = new DriveService(new BaseClientService.Initializer
                    {
                        // YOUR CODE SHOULD BE HERE..
                    });

            }
            else
        {
            return result.RedirectUri;
        }
    }

to call the above function asynchronously use something like:

var cancelToken = new CancellationTokenSource();
var z = Task.Factory.StartNew(() => IndexAsync(cancelToken.Token));
like image 44
beebee Avatar answered Nov 09 '22 01:11

beebee