I'm developing a web API that uses the google drive API, on my local machine, first run it redirected me to a google page to authenticate and then worked fine. Now I want to publish my code to Azure, and after I've published it the same function fails, because I could authenticate. How do I perform this authentication on the server?
I've followed the .NET QuickStart (https://developers.google.com/drive/api/v3/quickstart/dotnet), here is my code:
using (var stream =
new FileStream($#"{dir}\client_secret.json", FileMode.Open, FileAccess.Read))
{
var cred = GoogleClientSecrets.Load(stream).Secrets;
Credentials = GoogleWebAuthorizationBroker.AuthorizeAsync(
cred,
Scopes,
"user",
CancellationToken.None).Result;
}
service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = Credentials,
ApplicationName = ApplicationName,
});
}
The tutorial you are following is for a .NET console application this is a native application not a web application. GoogleWebAuthorizationBroker is for use with installed applications. It works on localhost because its able to spawn the browser window on your machine. This wont work in a hosted environment because it cant spawn a web browser on the server.
You should be following this example Web applications
using System;
using System.Web.Mvc;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2.Mvc;
using Google.Apis.Drive.v2;
using Google.Apis.Util.Store;
namespace Google.Apis.Sample.MVC4
{
public class AppFlowMetadata : FlowMetadata
{
private static readonly IAuthorizationCodeFlow flow =
new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "PUT_CLIENT_ID_HERE",
ClientSecret = "PUT_CLIENT_SECRET_HERE"
},
Scopes = new[] { DriveService.Scope.Drive },
DataStore = new FileDataStore("Drive.Api.Auth.Store")
});
public override string GetUserId(Controller controller)
{
// In this sample we use the session to store the user identifiers.
// That's not the best practice, because you should have a logic to identify
// a user. You might want to use "OpenID Connect".
// You can read more about the protocol in the following link:
// https://developers.google.com/accounts/docs/OAuth2Login.
var user = controller.Session["user"];
if (user == null)
{
user = Guid.NewGuid();
controller.Session["user"] = user;
}
return user.ToString();
}
public override IAuthorizationCodeFlow Flow
{
get { return flow; }
}
}
}
Note about azure you may have to change the location where filedatastore stores the credentials it depends upon where you have write access to. That can be done by supplying a path to the folder you want to store it in.
new FileDataStore(#"c:\datastore",true)
Related
I am trying to register an application in Azure AD using graph API, I have a method CallRestAPI which will make the request.
Below is the code
public async Task<Response> AzureADApp()
{
Response responseMessage = new Response();
try
{
var token = GenerateToken();
List<(string, string)> listHeaders = new List<(string, string)>();
listHeaders.Add(("Authorization", string.Concat("Bearer" + " " + token)));
listHeaders.Add(("Content-Type", "application/json"));
List<(string, string)> param = new List<(string, string)>();
param.Add(("displayName", "VS1Application123"));
param.Add(("homepage", "https://localhost:44358/"));
param.Add(("identifierUris", "https://G7CRM4L/6958490c-21ae-4885-804c-f03b3add87ad"));
string callUrl = "https://graph.windows.net/G7CRM4L/applications/?api-version=1.6";
var result = CallRestAPI(callUrl, "", Method.POST, listHeaders, param);
}
catch (Exception ex)
{
responseMessage.StatusCode = Convert.ToInt16(HttpStatusCode.InternalServerError);
}
return responseMessage;
}
public async Task<IRestResponse> CallRestAPI(string BaseAddress, string SubAddress, Method method, List<(string, string)> headersList = null, List<(string, string)> paramsList = null)
{
var call = new RestClient(BaseAddress + SubAddress);
var request = new RestRequest(method);
if (headersList != null)
{
foreach (var header in headersList)
{
request.AddHeader(header.Item1, header.Item2);
}
}
if (paramsList != null)
{
foreach (var param in paramsList)
{
request.AddParameter(param.Item1, param.Item2);
}
}
var response = call.ExecuteTaskAsync(request);
return response.Result;
}
I think the way I am sending parameters in the body is not correct can anyone guide me how to make this code work or is there a better way to achieve the same?
Thank you.
A better way to achieve the same i.e. register an app with Azure AD will be to make use of Azure AD Graph Client Library
I say it's a better approach because when you use the client library you reap multiple benefits like no raw HTTP request handling, writing more convenient and declarative C# code, depending on a well tested library, async support etc.
Underlying Graph API used will still be the same I suppose
POST https://graph.windows.net/{tenant-id}/applications?api-version=1.6
Here is sample code (C#) to create an Azure AD application
Notice that I've kept app.PublicClient flag as true to register as a native application. You can set it to false if you want to register it as a web application.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.ActiveDirectory.GraphClient;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
namespace CreateAzureADApplication
{
class Program
{
static void Main(string[] args)
{
ActiveDirectoryClient directoryClient;
ActiveDirectoryClient activeDirectoryClient = new ActiveDirectoryClient(new Uri("https://graph.windows.net/{yourAADGUID}"),
async () => await GetTokenForApplication());
Application app = new Application();
app.DisplayName = "My Azure AD Native App";
app.PublicClient = true;
app.Homepage = "https://myazureadnativeapp";
activeDirectoryClient.Applications.AddApplicationAsync(app).GetAwaiter().GetResult();
}
public static async Task<string> GetTokenForApplication()
{
AuthenticationContext authenticationContext = new AuthenticationContext(
"https://login.microsoftonline.com/{yourAADGUID}",
false);
// Configuration for OAuth client credentials
ClientCredential clientCred = new ClientCredential("yourappclientId",
"yourappclientsecret"
);
AuthenticationResult authenticationResult =
await authenticationContext.AcquireTokenAsync("https://graph.windows.net", clientCred);
return authenticationResult.AccessToken;
}
}
}
Setup: I have an application registered in Azure AD, which has required permissions as application permission - Read and Write all applications and grant permissions is done for this app. Now using this application's client id and client secret, a token is acquired and Azure AD Graph API is called to create an application. It is not mandatory to use application permissions, you can also use delegated permissions by prompting user for credentials. See links to more detailed examples (old ones but still useful).
Console Application using Graph client library
Web app calls Graph using Graph client library
Azure AD Graph Client Library 2.0 Announcement page
On a side note, you could do this using the newer Microsoft Graph API as well,
POST https://graph.microsoft.com/beta/applications
but the ability to create applications is still in beta and hence not recommeded for production workloads. So even though Microsoft Graph API would be recommende for most scenarios, at least for this one, using Azure AD Graph API is the way to go currently.
I have covered this in a little more detail in a similar SO Post here.
To automate updating a Google Fusion Table, we have created a .NET console application to which we have attached the file clientsecrets.json generated and downloaded from the Google administration console.
When I run the application locally, a browser window opens to authorize the use of the API with OAuth 2.0. Once it authorized the process executed properly.
However, when we run the application on the server where we want to schedule your daily execution, does not open the browser window and stating "One or more errors ocurred".
The server is a Windows Server 2012. The application is built on the .NET 4.5.1 and authorizing the code is as follows:
...
var service = new FusiontablesService(new BaseClientService.Initializer
{
ApplicationName = "Fusion Tables Sample",
HttpClientInitializer = Utils.Google.GetCredential().Result
});
...
public static async Task<UserCredential> GetCredential()
{
using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
{
return await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
new[] { FusiontablesService.Scope.Fusiontables },
"XXXXXXXXXXXX[user]", CancellationToken.None);
}
}
Seems you need a service account that can be created from Google "IAM and Admin' Console: https://console.cloud.google.com/iam-admin/serviceaccounts. Once the service account is created, save its private key locally (preferably as JSON-file) and then create BaseClientService.Initializer from the credentials obtained from this file. Something like:
var scopes = new[] { FusiontablesService.Scope.Fusiontables };
using (var stream = new FileStream(keyFilePath, FileMode.Open, FileAccess.Read))
{
return GoogleCredential.FromStream(stream)
.CreateScoped(scopes);
}
Having such Initializer, just create the instance of FusiontablesService almost as you did:
service = new FusiontablesService(
new BaseClientService.Initializer()
{
HttpClientInitializer = creds,
ApplicationName = "Street Parking",
});
I am trying to access Google Analytics data for ga:visits and ga:transactions using OAUTH 2.0 authentication. I have followed the following steps :
Enabled the Google Analytics API on the https://code.google.com/apis/console/ website.Also, created a client ID and saved the .p12 key
The generated email id was provided Read and Analyze access on Analytics website.
I have created a Windows form application and also downloaded the NuGet package using the package command:- Install-Package Google.Apis.Analytics.v3
All the references including Google.Apis,Google.Apis.Analytics.v3,Google.Apis.Auth,Google.Apis.Auth.PlatformServices,Google.Apis.Core,Google.Apis.PlatformServices are added into the project
Further, while accessing the data am facing an error :-
Locating source for
c:\code\google.com\google-api-dotnet-client\default\Tools\Google.Apis.Release\bin\Debug\test\default\Src\GoogleApis.Auth.DotNet4\OAuth2\ServiceAccountCredential.cs
while the following line of code is run :
var credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { AnalyticsService.Scope.AnalyticsReadonly }
}.FromCertificate(cert));
Could you please help me with this error.
Please find below the code used :-
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Google.Apis.Analytics.v3;
using System.Security.Cryptography.X509Certificates;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
namespace WindowsFormsApplication5
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
string keyFilePath = "C:\\Fetch GA Data-348e7435108b.p12";
string serviceAccountEmail= "xx#developer.gserviceaccount.com";
string keyPassword = "notasecret";
string websiteCode = "8482xxxx";
AnalyticsService service = null;
//loading the Key file
var certificate = new X509Certificate2(keyFilePath, keyPassword, X509KeyStorageFlags.Exportable);
//Add Scopes
var scopes =
new string[] { AnalyticsService.Scope.Analytics,AnalyticsService.Scope.AnalyticsEdit,AnalyticsService.Scope.AnalyticsManageUsers,AnalyticsService.Scope.AnalyticsReadonly
};
//create a new ServiceAccountCredential
var credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
//Scopes = scopes
Scopes = new[] { AnalyticsService.Scope.AnalyticsReadonly }
}.FromCertificate(cert));
//Create a Service
service = new AnalyticsService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential
});
DataResource.GaResource.GetRequest request = service.Data.Ga.Get(
"ga:" + websiteCode, "2015-05-27", "2015-05-27","ga:visits");
request.Dimensions = "ga:year,ga:month,ga:day";
var data = request.Execute();
}
}
}
I am not sure where you got that example from but its not even close to how you should be using a service account to access Google Analytics api.
string[] scopes =
new string[] {
AnalyticsService.Scope.Analytics, // view and manage your Google Analytics data
AnalyticsService.Scope.AnalyticsManageUsers}; // View Google Analytics data
string keyFilePath = #"c:\file.p12" ; // found in developer console
string serviceAccountEmail = "xx#developer.gserviceaccount.com"; // found in developer console
//loading the Key file
var certificate = new X509Certificate2(keyFilePath, "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential( new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = scopes
}.FromCertificate(certificate));
AnalyticsService service = new AnalyticsService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Analytics API Sample",
});
code ripped from Google Analytics Authentication C# tutorial series
TIP Service Account Setup
Take the service account email address from the Google Developer console, we need to give this email address access to our Google Analytics Data. We do that by adding it as we would any other user in the Admin Section of the Google Analytics Website. Add the service account email address as a user at the account level it must be the account level. This will enable your service account access to the Google Analytics account in question. Did I mention it must be at the ACCOUNT Level?
I need to be able to use a refresh token to be able to re-authenticate a token after the access token has expired. How can I do this using the C# v3 API? I've looked at the UserCredential class and AuthorizationCodeFlow class and nothing is jumping out at me.
I'm using the following code to authenticate it originally.
var result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).
AuthorizeAsync(CancellationToken.None);
if (result.Credential != null)
{
var service = new YouTubeService(new BaseClientService.Initializer
{
HttpClientInitializer = result.Credential,
ApplicationName = "YouTube Upload Tool"
});
}
And this is my AppFlowMetadata class.
public class AppFlowMetadata : FlowMetadata
{
private static readonly IAuthorizationCodeFlow flow =
new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "ID",
ClientSecret = "SECRET",
},
Scopes = new[] { YouTubeService.Scope.YoutubeUpload },
DataStore = new EFDataStore(-1) // A data store I implemented using Entity Framework 6.
});
public override string GetUserId(Controller controller)
{
return "test";
}
public override IAuthorizationCodeFlow Flow
{
get { return flow; }
}
}
If anyone can suggest anything, I would greatly appreciate it. Thank you.
While this is not an answer, this is how I got around it. I had to create the GET request for authorisation (redirect your user to the url you get back and set your Controller Action to receive the callback specified in your Google Developer Console) and the PUT request for the Token (which I then stored using EF6) manually. I used System.Net.Http.HttpClient to make these requests, which was quite straight forward. See this link for all the details I needed to get this working.
It was the only way I could set the access_type to "offline". If the .NET API does this, I'm still curious to find out how.
With the token data stored, I now use the API to validate and refresh the token when I need to. I actually did this in a server side console application rather than a MVC app (hence the EF token persistence).
UserCredential credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = "ID",
ClientSecret = "Secret"
},
new[] { YouTubeService.Scope.YoutubeUpload },
"12345",
CancellationToken.None,
new EFDataStore(-1) // My own implementation of IDataStore
);
// This bit checks if the token is out of date,
// and refreshes the access token using the refresh token.
if(credential.Token.IsExpired(SystemClock.Default))
{
if (!await credential.RefreshTokenAsync(CancellationToken.None))
{
Console.WriteLine("No valid refresh token.");
}
}
var service = new YouTubeService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "MY App"
});
I hope this helps others.
I have a desktop application to read mail using GMAIL API over REST Interface. I want to use service account so that we can download the mails using domain setting and user interaction is null. I am successfully able to create Gmail Service instance but when I try to access any Gmail API method like fetching mail list or any other I get an exception saying
Google.Apis.Auth.OAuth2.Responses.TokenResponseException:
Error:"access_denied", Description:"Requested client not
authorized."
I am done with all the setting at developer console and added scopes to my gapps domain.
Does Gmail API support service account? Using the same setting and service account I am able to get list of all files in Google drive using Drive service and API.
I use the following C# code for accessing Gmail from Service Account
String serviceAccountEmail =
"999999999-9nqenknknknpmdvif7onn2kvusnqct2c#developer.gserviceaccount.com";
var certificate = new X509Certificate2(
AppDomain.CurrentDomain.BaseDirectory +
"certs//fe433c710f4980a8cc3dda83e54cf7c3bb242a46-privatekey.p12",
"notasecret",
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
string userEmail = "user#domainhere.com.au";
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
User = userEmail,
Scopes = new[] { "https://mail.google.com/" }
}.FromCertificate(certificate)
);
if (credential.RequestAccessTokenAsync(CancellationToken.None).Result)
{
GmailService gs = new GmailService(
new Google.Apis.Services.BaseClientService.Initializer()
{
ApplicationName = "iLink",
HttpClientInitializer = credential
}
);
UsersResource.MessagesResource.GetRequest gr =
gs.Users.Messages.Get(userEmail, msgId);
gr.Format = UsersResource.MessagesResource.GetRequest.FormatEnum.Raw;
Message m = gr.Execute();
if (gr.Format == UsersResource.MessagesResource.GetRequest.FormatEnum.Raw)
{
byte[] decodedByte = FromBase64ForUrlString(m.Raw);
string base64Encoded = Convert.ToString(decodedByte);
MailMessage msg = new MailMessage();
msg.LoadMessage(decodedByte);
}
}
Here is a little bit of python 3.7:
from google.oauth2 import service_account
from googleapiclient.discovery import build
def setup_credentials():
key_path = 'gmailsignatureproject-zzz.json'
API_scopes =['https://www.googleapis.com/auth/gmail.settings.basic',
'https://www.googleapis.com/auth/gmail.settings.sharing']
credentials = service_account.Credentials.from_service_account_file(key_path,scopes=API_scopes)
return credentials
def test_setup_credentials():
credentials = setup_credentials()
assert credentials
def test_fetch_user_info():
credentials = setup_credentials()
credentials_delegated = credentials.with_subject("tim#vci.com.au")
gmail_service = build("gmail","v1",credentials=credentials_delegated)
addresses = gmail_service.users().settings().sendAs().list(userId='me').execute()
assert gmail_service
If you want to "read mail" you'll need the newer Gmail API (not the older admin settings API that 'lost in binary' pointed out). Yes you can do this with oauth2 and the newer Gmail API, you need to whitelist the developer in Cpanel and create a key you can sign your requests with--it take a little bit to setup:
https://developers.google.com/accounts/docs/OAuth2ServiceAccount#formingclaimset
For C# Gmail API v1, you can use the following code to get the gmail service. Use gmail service to read emails. Once you create the service account in Google Console site, download the key file in json format. Assuming the file name is
"service.json".
public static GoogleCredential GetCredenetial(string serviceAccountCredentialJsonFilePath)
{
GoogleCredential credential;
using (var stream = new FileStream(serviceAccountCredentialJsonFilePath, FileMode.Open, FileAccess.Read))
{
credential = GoogleCredential.FromStream(stream)
.CreateScoped(new[] {GmailService.Scope.GmailReadonly})
.CreateWithUser(**impersonateEmail#email.com**);
}
return credential;
}
public static GmailService GetGmailService(GoogleCredential credential)
{
return new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Automation App",
});
}
// how to use
public static void main()
{
var credential = GetCredenetial("service.json");
var gmailService = GetGmailService(credential);
// you can use gmail service to retrieve emails.
var mMailListRequest = gmailService.Users.Messages.List("me");
mMailListRequest.LabelIds = "INBOX";
var mailListResponse = mMailListRequest.Execute();
}
Yes you can... check the delegation settings...
https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account
Edit: Use the link Eric DeFriez shared.
You can access any user#YOUR_DOMAIN.COM mails/labels/threads etc. with the new Gmail API:
https://developers.google.com/gmail/api/
via service account with impersonation (service account is accessing api as if it was specific user from your domain).
See details here: https://developers.google.com/identity/protocols/OAuth2ServiceAccount
Here is relevant code in Dartlang:
import 'package:googleapis_auth/auth_io.dart' as auth;
import 'package:googleapis/gmail/v1.dart' as gmail;
import 'package:http/http.dart' as http;
///credentials created with service_account here https://console.developers.google.com/apis/credentials/?project=YOUR_PROJECT_ID
final String creds = r'''
{
"private_key_id": "FILL_private_key_id",
"private_key": "FILL_private_key",
"client_email": "FILL_service_account_email",
"client_id": "FILL_client_id",
"type": "service_account"
}''';
Future<http.Client> createImpersonatedClient(String impersonatedUserEmail, List scopes) async {
var impersonatedCredentials = new auth.ServiceAccountCredentials.fromJson(creds,impersonatedUser: impersonatedUserEmail);
return auth.clientViaServiceAccount(impersonatedCredentials , scopes);
}
getUserEmails(String userEmail) async { //userEmail from YOUR_DOMAIN.COM
var client = await createImpersonatedClient(userEmail, [gmail.GmailApi.MailGoogleComScope]);
var gmailApi = new gmail.GmailApi(client);
return gmailApi.users.messages.list(userEmail, maxResults: 5);
}