How to use Google Apis using existing Access Token in Xamarin? - c#

I implemented login process successfully in my Xamarin forms app using Xamarin.auth. now I want to connect to Google APIs and upload AppData. here is the Code I tried,
I tread to fetch the GoogleCredential using token and providing this Credential to Google API but it failed.
var store = AccountStore.Create();
var SavedAccount = store.FindAccountsForService(GoogleDriveBackup.Auth.Constants.AppName).FirstOrDefault();
GoogleCredential credential2 = GoogleCredential.FromAccessToken(SavedAccount.Properties["access_token"]);
var driveService = new Google.Apis.Drive.v3.DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential2,
ApplicationName = "myApp",
});
FilesResource.CreateMediaUpload request;
var filePath = Path.Combine(path, filename);
using (var stream = new System.IO.FileStream(filePath,
System.IO.FileMode.Open))
{
request = driveService.Files.Create(
fileMetadata, stream, "application/json");
request.Fields = "id";
request.Upload();
}
var file = request.ResponseBody;
request.ResponseBody is always null. I thought that it has something to do with credentials.
I tried using
var store = AccountStore.Create();
var SavedAccount = store.FindAccountsForService(GoogleDriveBackup.Auth.Constants.AppName).FirstOrDefault();
var flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "xxxx-xxx.apps.googleusercontent.com",
ClientSecret = "xxxxxxxx"
}
});
Google.Apis.Auth.OAuth2.Responses.TokenResponse responseToken = new Google.Apis.Auth.OAuth2.Responses.TokenResponse()
{
AccessToken = SavedAccount.Properties["access_token"],
ExpiresInSeconds = Convert.ToInt64(SavedAccount.Properties["expires_in"]),
RefreshToken = SavedAccount.Properties["refresh_token"],
Scope = DriveService.Scope.DriveAppdata,
TokenType = SavedAccount.Properties["token_type"],
};
var token= SavedAccount.Properties["access_token"];
var credential = new UserCredential(flow, "", responseToken);
But above case requires Client Secret which I don't have as I created "Android App" in the google console and signed in using on ClientId. So I read somewhere that I should create "Others" in the google console and use ClientId and Client Secret from there which makes not much sense to me because I am logged in with different client id's. Anyway, I tried that also but the response was null.
So what is the deal here? How can achieve my goal?

The google .net client library doesn't support xamarin. I am actually surprised you got it working this far. The main issue you are going to have is the authentication as you have already noticed the credential type for the .net client library is going to be either browser, native or api key. The mobile (Android Ios) clients arnt going to work as you dont have a secret the method of authentication is different and the client library doesn't have ability to do this.
The only suggestion i would have would be to work out Oauth2 authentication with xamarin on your own and then build the TokenResponse as you are doing now. You may then be able to feed that token to the Google .net client library if you can get the dlls into your project.
To my knowlage we have no plans to support xamarinwith the Google .net clinet library in the near future please see 984 840 1167

Related

I just want to access some files from my .net core web application using Microsoft Graph and nothing is current

I have a .net core web application and I just want to access some files that are on one of our SharePoint sites, using Microsoft Graph. I've looked at courses on Pluralsight and the most current course has outdated material. I'm looking for a simple code example that gets me from a - z and I can't find any information that exists before mid 2022! On a similar question, I got an answer with code that didn't even work. Apparently I have to get an authorization code, in order to get an access token. BUT, the authorization code pretty much expires as soon as the user is logged into my application. Below is a modified version of the code I was given. I modified it in an effort to try to make it work. As you will see, I tried various version of "scopes" and I'm getting a token that I'm trying to use in the AuthorizationCode Credentials. I don't know if it's the right token to use. I've also seen some examples using PostMan. Getting things to work in postman is absolutely wonderful, but it's not C# code. I apologize if I seem a little rough, I'm just extremally frustrated. It should not be this difficult to find a working code sample. Any help would be appreciated. Here is the code I have that doesn't work:
//var scopes = new[] { "https://mysite.sharepoint.com/.default" };
//var scopes = new[] { "https://graph.microsoft.com/.default" };
var scopes = new[] { "https://graph.microsoft.com/User.ReadWrite.All" };
var tenantId = "tenant";
var clientId = "clientId";
var clientSecret = "shhItsASecret";
var client = new RestClient("https://login.microsoftonline.com/siteId/oauth2/v2.0/token");
var request = new RestRequest();
request.Method = Method.Post;
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddParameter("client_id", clientId);
request.AddParameter("client_secret", clientSecret);
request.AddParameter("scope", "https://graph.microsoft.com/User.ReadWrite.All");
request.AddParameter("response_type", "code");
request.AddParameter("grant_type", "client_credentials");
RestResponse response = client.Execute(request);
TokenModel tokenModel = new TokenModel();
JsonConvert.PopulateObject(response.Content, tokenModel);
var authorizationCode = tokenModel.access_token;
// using Azure.Identity;
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
// https://learn.microsoft.com/dotnet/api/azure.identity.authorizationcodecredential
var authCodeCredential = new AuthorizationCodeCredential(tenantId, clientId, clientSecret, authorizationCode, options);
Azure.Core.AccessToken accessToken = new Azure.Core.AccessToken();
try
{
accessToken = await authCodeCredential.GetTokenAsync(new Azure.Core.TokenRequestContext(scopes) { });
}
catch (System.Exception ex)
{
throw;
}
var tok = accessToken;
UPDATE:
I now know that I need to use delegated permissions and I need to use the auth code flow in order to do that. However, we use 2 factor authentication and it seems that by the time I can read anything from a variable, I can only see an access-token. If I understand correctly, the auth code is used to get an access-token and it expires. So, I can't seem to use that. Could I pass that access-token to my code that instantiates the graphService?
Someone else suggested I need to adjust my startup file and my appsettings file. I can't really do that. We have 5 other modules in our web application and this would be a big change to all of that. So, I'm not sure what I should be doing there. Bellow is what is in our startup, as it pertains to authentication:
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<UnitRateContractSystemContext>()
.AddDefaultTokenProviders()
.AddUserStore<UserStore<ApplicationUser, ApplicationRole, UnitRateContractSystemContext, Guid, ApplicationUserClaim<Guid>, ApplicationUserRole, IdentityUserLogin<Guid>, IdentityUserToken<Guid>, IdentityRoleClaim<Guid>>>()
.AddRoleStore<RoleStore<ApplicationRole, UnitRateContractSystemContext, Guid, ApplicationUserRole, IdentityRoleClaim<Guid>>>();
UPDATE 3:
I looked a little further down in my startup file and there was some openID connect information. Not sure why it was moved so far down, but I moved it up. Below is my entire authentication setup. The last 4 lines I added as a result of following one of the examples that someone provided. It builds just fine, but when I run it, I get an error in the Program.cs file: System.InvalidOperationException: 'Scheme already exists: Cookies'. If I go and comment out the "AddCookie()" line I get a similar error, but it says that OpenId Connect exists. So, at this point I'm stuck, but I feel if this can be solved, it might be the solution.
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<UnitRateContractSystemContext>()
.AddDefaultTokenProviders()
.AddUserStore<UserStore<ApplicationUser, ApplicationRole, UnitRateContractSystemContext, Guid, ApplicationUserClaim<Guid>, ApplicationUserRole, IdentityUserLogin<Guid>, IdentityUserToken<Guid>, IdentityRoleClaim<Guid>>>()
.AddRoleStore<RoleStore<ApplicationRole, UnitRateContractSystemContext, Guid, ApplicationUserRole, IdentityRoleClaim<Guid>>>();
#region Authentication
string[] initialScopes = Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');
//auth
services.AddAuthentication(options =>
{
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.Authority = Configuration["Authentication:Microsoft:OAuth"];
options.RequireHttpsMetadata = true;
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.UsePkce = false;
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("email");
options.SaveTokens = true;
options.CallbackPath = new PathString(Configuration["Authentication:Microsoft:Callback"]);
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
};
// MetadataAddress represents the Active Directory instance used to authenticate users.
options.MetadataAddress = Configuration["Authentication:Microsoft:Meta"];
options.ClientId = Configuration["Authentication:Microsoft:ApplicationId"];
options.ClientSecret = Configuration["Authentication:Microsoft:Password"];
})
.AddMicrosoftIdentityWebApp(Configuration)
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();
You have an asp.net core web application, and you want to access some files that are on one of your SharePoint sites. So I think you may want to use this graph api with request /sites/{site-id}/drive/items/{item-id}. If you want to use other APIs, the steps are the same.
First, since the scenario for you is access files in different sites, so if you used delegated permission(require users sign in first and get access token on behalf the user), you may meet an issue that the user is not allowed to this site so that he can't access the site. I'm afraid this is what you want, so you can use application permissions. For this api, the permission is like below, please add api permissions in Azure AD first.
Then, since you have an asp.net core web application, then you can use Azure identity + graph SDK to do this. You can use code below:
using Microsoft.Graph;
using Azure.Identity;
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "tenant_name.onmicrosoft.com";
var clientId = "aad_app_id";
var clientSecret = "client_secret";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var file = await graphClient.Sites["site_id"].Drive.Items["item_id"].Request().GetAsync();
If you want to let user sign in and then list some files which is allowed to the signed in user, the easiest way is adding Microsoft identity platform into your application, which can created by a template, just need to choose the authentication option when creating application in visual studio, then update the configurations. And certainly, you need to give delegated API permission, which is different than above.
Finally here's the official sample, you can see what codes/packages/configurations are added based on a web application.

OAuth2 Implicit Flow with C# Windows Forms

I'm developing a c# windows forms app that needs to authenticate using the Implicit Flow (The client does not accept another flow). As requirement, I need to open the default system browser to authenticate (so no embedded web view on the application)
I'm trying to use the OidcClient C# and the Samples but I can't get it to work.
The closest I got was using the ConsoleSystemBrowser. But using the code below I get always an UnknownError with empty response.
I can see in the browser the id_token: http://127.0.0.1:54423/auth/signin-oidc#id_token=XXX. How can I read it?
var browser = new SystemBrowser();
var redirectUri = string.Format($"http://127.0.0.1:{browser.Port}/auth/signin-oidc");
var options = new OidcClientOptions
{
Authority = "https://demo.identityserver.io",
ClientId = "implicit",
Scope = "openid profile api",
RedirectUri = redirectUri,
Browser = browser
};
var client = new OidcClient(options);
var state = await client.PrepareLoginAsync(new Dictionary<string, string>()
{
{ OidcConstants.AuthorizeRequest.ResponseType, OidcConstants.ResponseTypes.IdTokenToken}
});
var browserOption = new BrowserOptions(state.StartUrl, redirectUri)
{
Timeout = TimeSpan.FromSeconds(300),
DisplayMode = DisplayMode.Hidden,
ResponseMode = OidcClientOptions.AuthorizeResponseMode.Redirect
};
var result = await browser.InvokeAsync(browserOption, default);
result.ResultType => BrowserResultType.UnknownError
Your application should register a private URL scheme with the networking component of the OS. Then, URLs of the form "x-my-app://xxx" will be forwarded to your application. (And you register the URL with the OAuth IdP so it works as a redirect URL.)
For Windows, it appears that Microsoft calls this "Pluggable Protocols". See
programming-pluggable-protocols
An older doc
A source of code examples for this pattern might be from the github desktop application--it is open source and registers its own scheme with Windows.
It registers the private scheme x-github-client You can see how it's done in the source also see here

Server-side task to query Office 365 account for new emails

I need a server-side task on my .NET 4.6.1/MVC 5 app that will periodically check a specific O365 email address for new emails and retrieve them if found. This seems like a stupidly simple task, but I cannot find documentation anywhere for creating a server-side process to accomplish this. The only documentation Microsoft seems to have is for OAuth2 and passing through credentials when users sign in. I don't want that. I want to check one specific account, that's it. How would I accomplish this?
These are the pages I've found. There are others, but all are along these lines.
Intro to the Outlook API - I don't see a way to use a service account with the v2 endpoint.
Get Started with the Outlook REST APIs - This is specific to logging users in with OAuth2, unhelpful for my purposes.
Intro to the Outlook API - I don't see a way to use a service account with the v2 endpoint.
The v2 endpoint doesn’t support client credential at present( refer to the limitation). You need to register/configure the app using Azure portal and use the original endpoint to authenticate the app. More detail about register the app please refer to here. And we need to ‘read mail in all mailbox’ to use the client credential to read the messages like figure below.
And here is the code that using client credential to read messages using the Microsoft Graph:
string clientId = "";
string clientsecret = "";
string tenant = "";
string resourceURL = "https://graph.microsoft.com";
string authority = "https://login.microsoftonline.com/" + tenant + "/oauth2/token";
string userMail = "";
var accessToken = new TokenHelper(authority).AcquireTokenAsync(clientId, clientsecret, resourceURL);
var graphserviceClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
(requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
return Task.FromResult(0);
}));
var items = await graphserviceClient.Users[user].Messages.Request().OrderBy("receivedDateTime desc").GetAsync();
foreach (var item in items)
{
Console.WriteLine(item.Subject);
}
class TokenHelper
{
AuthenticationContext authContext;
public TokenHelper(string authUri)
{
authContext = new AuthenticationContext(authUri);
}
public string AcquireTokenAsync(string clientId, string secret,string resrouceURL)
{
var credential = new ClientCredential(clientId: clientId, clientSecret: secret);
var result = authContext.AcquireTokenAsync(resrouceURL, credential).Result;
return result.AccessToken;
}
}
In addition, if we authenticate the app with code grant flow we can also create a subscription which notify the app when the mail box receive the new messages.( refer to webhoocks/subscription)

YouTube v3 API caption download using SDK nuget package

I'm trying to download a caption track using YouTube API v3 (https://developers.google.com/youtube/v3/docs/captions/download) and official .NET SDK nuget package (https://www.nuget.org/packages/Google.Apis.YouTube.v3/, version 1.9.0.1360).
Returned stream contains the following text:
"The OAuth token was received in the query string, which this API forbids for response formats other than JSON or XML. If possible, try sending the OAuth token in the Authorization header instead."
instead of the SRT plain text content which I just uploaded and verified manually through YouTube.com UI.
I found the type of error: lockedDomainCreationFailure
My code:
...
_service = new YTApi.YouTubeService(new BaseClientService.Initializer {
ApplicationName = config.AppName,
ApiKey = config.DeveloperKey
});
...
public Stream CaptionsDownload(
string accessToken,
string trackId
)
{
var request = _service.Captions.Download(trackId);
request.OauthToken = accessToken;
request.Tfmt = YTApi.CaptionsResource.DownloadRequest.TfmtEnum.Srt;
var trackStream = new MemoryStream();
request.Download(trackStream);
trackStream.Position = 0;
return trackStream;
}
I cannot seem to find the way to set any headers on _service.HttpClient, and I guess I shouldn't do it manually. I expect that DownloadRequest (or YouTubeBaseServiceRequest) will put
/// <summary>
/// OAuth 2.0 token for the current user.
/// </summary>
[RequestParameter("oauth_token", RequestParameterType.Query)]
public virtual string OauthToken { get; set; }
into a correct authorization header. I don't see this implemented in the version 1.9.0.1360.
Maybe I'm overlooking something? Any help is greatly appreciated.
Note: I use other caption-related methods with this SDK, and 'download' is the only one I'm having a trouble with.
You initialed the service WITHOUT the user credential (you only used the API key). Take a look in one of the samples in our developers guide, (and pick the right flow... are you using installed application, windows phone, etc.?)
You will have to change the way you create your service to do something like the following:
UserCredential credential;
using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
new[] { YoutubeService.Scope.<THE_RIGHT_SCOPE_HERE> },
"user", CancellationToken.None);
}
// Create the service.
_service = new YouTubeService(new BaseClientService.Initializer {
ApplicationName = config.AppName,
HttpClientInitializer = credential,
ApplicationName = "Books API Sample",
});
Then, for each request to the youtube service, your OAuth access token will be included as an additional header on the HTTP request itself.

Unable To upload file to google drive - using c#

All code runs without errors, but when I check my Google Drive account I can't find the file I am uploading ("document.txt").
Also it has asked me for Authentication again.
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = "Here my clientid",
ClientSecret = "client secret",
},
new[] { DriveService.Scope.Drive },
"user",
CancellationToken.None).Result;
// Create the service.
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Drive API Sample",
});
File body = new File();
body.Title = "My document";
body.Description = "A test document";
body.MimeType = "text/plain";
byte[] byteArray = System.IO.File.ReadAllBytes("document.txt");
System.IO.MemoryStream stream = new System.IO.MemoryStream(byteArray);
FilesResource.InsertMediaUpload request = service.Files.Insert(body, stream, "text/plain");
request.Upload();
File file = request.ResponseBody;
Questions:
Why cant I find my uploaded file, and how can I get it to remember my authentication.
I think you are forgetting body.Parent so it doesn't know what directory to place the file into.
parents[] list Collection of parent folders which contain this file.
Setting this field will put the file in all of the provided folders.
On insert, if no folders are provided, the file will be placed in the
default root folder.
example:
body.Parents = new List<ParentReference>() { new ParentReference() { Id = 'root' } };
You are getting asked for authentication again because you aren't saving authentication.
//Scopes for use with the Google Drive API
string[] scopes = new string[] { DriveService.Scope.Drive,
DriveService.Scope.DriveFile};
// here is where we Request the user to give us access, or use the Refresh Token that was previously stored in %AppData%
UserCredential credential =
GoogleWebAuthorizationBroker
.AuthorizeAsync(new ClientSecrets { ClientId = CLIENT_ID
, ClientSecret = CLIENT_SECRET }
,scopes
,Environment.UserName
,CancellationToken.None
,new FileDataStore("Daimto.GoogleDrive.Auth.Store")
).Result;
FileDataStore stores the authentication data in the %appdata% directory.
More detailed information can be found in the tutorial Google Drive API with C# .net – Upload
Update For the following error:
"The API is not enabled for your project, or there is a per-IP or
per-Referer restriction configured on your API key and the request
does not match these restrictions. Please use the Google Developers
Console to update your configuration. [403]"
Go to Developer console for your project here Under APIs & auth -> APIs enable Google drive API and sdk. Also go to credentials and make sure you added a product name and email.

Categories