Problem with local development for google-oauth / calendar api - c#

I "just" want to integrate google calendar api to my little web project. The user should be able to add calendar entries to his calendar - via a c# core (2.2) mvc project. The problem is I can't find any complete example how to do this and tried a lot without any solution.
The main problem - how can I get the permission? And how can I set the redirect url?
Why google does not provide a complete example für c# core?
I build a simple console projekt (based on an example) - that works if I set the permission manually. But I must ask my user to give the permission.
Btw - I created and saved the ClientId, ClientSecret and so on at/from https://console.developers.google.com/.
Thanks Ralf
public IActionResult GoogleCalendar(string id)
{
string refreshToken = string.Empty;
string credentialError;
var credential = GetUserCredential(out credentialError);
if (credential != null && string.IsNullOrWhiteSpace(credentialError))
{
//Save RefreshToken into Database
refreshToken = credential.Token.RefreshToken;
}
string addEventError;
string calendarEventId = string.Empty;
calendarEventId = AddCalenderEvents(refreshToken, "mytestuser#googlemail.com", "Test-Event " + DateTime.Now, DateTime.Now.AddMinutes(5), DateTime.Now.AddHours(2), out addEventError);
return View();
}
public static UserCredential GetUserCredential(out string error)
{
UserCredential credential = null;
error = string.Empty;
try
{
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = ClientId,
ClientSecret = ClientSecret
},
Scopes,
"mytestuser#googlemail.com",
CancellationToken.None,
new FileDataStore("Google Oauth2 Client App")).Result;
}
catch (Exception ex)
{
credential = null;
error = "Failed to UserCredential Initialization: " + ex.ToString();
}
return credential;
}
I get from google
That’s an error.
Error: invalid_client
The OAuth client was not found.
On a (changing) local port an url I have never set.
access_type=offline
response_type=code
client_id=xxxxxx-yyyyyyyyyyyyy.apps.googleusercontent.com
redirect_uri=http://127.0.0.1:56253/authorize/
scope=https://www.googleapis.com/auth/calendar

The provided samples from google are quite confusing, the provided console demo application only works for local environments because it tries to launch the browser and use it to authenticatie.
To authenticatie users on a web app, check the following google guide: https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#web-applications-aspnet-mvc

Related

How to configure both form authentication and azure authentication in same application (office 365)?

Hello I have developed a Microsoft application using Microsoft Graph API in order to obtain planner data and store it in a database for now. On it's own the application works fine without any issue what so ever.
The next task for me is to integrate this separate application into the main company application. The main company's website uses form authentication. What is the best way to integrate this. Currently when I try to login to get authorized I am redirected to the form login not the Microsoft one
I have registered the application in the Microsoft application registration pool. I have also added the office 365 api
This is the token obtain code that i am using
public async Task<string> GetUserAccessTokenAsync()
{
string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
tokenCache = new SessionTokenCache(
signedInUserID,
HttpContext.Current.GetOwinContext().Environment["System.Web.HttpContextBase"] as HttpContextBase);
//var cachedItems = tokenCache.ReadItems(appId); // see what's in the cache
ConfidentialClientApplication cca = new ConfidentialClientApplication(
appId,
redirectUri,
new ClientCredential(appSecret),
tokenCache);
try
{
AuthenticationResult result = await cca.AcquireTokenSilentAsync(scopes.Split(new char[] { ' ' }));
return result.Token;
}
// Unable to retrieve the access token silently.
catch (MsalSilentTokenAcquisitionException)
{
HttpContext.Current.Request.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
throw new Exception(Resource.Error_AuthChallengeNeeded);
}
}
This is the sign in method I am trying use when trying to directly log in
// Signal OWIN to send an authorization request to Azure.
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
I have solved this issue by implementing the following code
public ActionResult SignIn()
{
var authContext = new AuthenticationContext("https://login.microsoftonline.com/common");
string redirectUri = Url.Action("Authorize", "Planner", null, Request.Url.Scheme);
Uri authUri = authContext.GetAuthorizationRequestURL("https://graph.microsoft.com/", SettingsHelper.ClientId,
new Uri(redirectUri), UserIdentifier.AnyUser, null);
// Redirect the browser to the Azure signin page
return Redirect(authUri.ToString());
}
public async Task<ActionResult> Authorize()
{
// Get the 'code' parameter from the Azure redirect
string authCode = Request.Params["code"];
AuthenticationContext authContext = new AuthenticationContext(SettingsHelper.AzureADAuthority);
// The same url we specified in the auth code request
string redirectUri = Url.Action("Authorize", "Planner", null, Request.Url.Scheme);
// Use client ID and secret to establish app identity
ClientCredential credential = new ClientCredential(SettingsHelper.ClientId, SettingsHelper.ClientSecret);
try
{
// Get the token
var authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(
authCode, new Uri(redirectUri), credential, SettingsHelper.O365UnifiedResource);
// Save the token in the session
Session["access_token"] = authResult.AccessToken;
return Redirect(Url.Action("Index", "Planner", null, Request.Url.Scheme));
}
catch (AdalException ex)
{
return Content(string.Format("ERROR retrieving token: {0}", ex.Message));
}
}
A link to the solution that helped tackle this was this. It's slightly old but still helped out massively
https://www.vrdmn.com/2015/05/using-office-365-unified-api-in-aspnet.html

Swagger UI will return API data, but my Authorized calls return "permission denied"

So I believe my APIservice should be fine since I can return results through Swagger? I am calling from a WPF project. I launch the program and it asks me to login, then it continues and will tell me I don't have permission.
I'm super green to WebAPI2 and think I may just be constructing my call incorrectly. It does seem that I get a token back correctly from my site, the only issue is when I try to actually call on the API for data.
Here is my code:
public static string clientId = "{#Calling App Id}";
public static string commonAuthority = "https://login.windows.net/{#my Azure AD tenant}";
public static Uri returnUri = new Uri("http://MyDirectorySearcherApp");
const string ResourceUri = "https://{#Api App Service}.azurewebsites.net";
public static async Task<List<User>> LoadBands(IPlatformParameters parent)
{
AuthenticationResult authResult = null;
List<User> results = new List<User>();
try {
//get token or use refresh
AuthenticationContext authContext = new AuthenticationContext(commonAuthority);
if (authContext.TokenCache.ReadItems().Count() > 0)
authContext = new AuthenticationContext(authContext.TokenCache.ReadItems().First().Authority);
authResult = await authContext.AcquireTokenAsync(ResourceUri, clientId, returnUri, parent);
} catch (Exception ee) {
throw ex;
}
using (var httpClient = new HttpClient()) {
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, $"{ResourceUri}/api/Band/")) {
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
using (var response = await httpClient.SendAsync(request)) {
string responseData = await response.Content.ReadAsStringAsync();
//responseData always equals "You do not have permission to view this directory or page"
return results;
}
}
}
Edit: Maybe helpful to note I'm using a DataAPI that is called by a Rest API, the rest API is secured by Azure AD.
Edit: I'm calling from a Portable Class Library.
Edit: Well, I'm getting authenticated but it does not appear to make any difference. If I completely remove the Auth header I get the same result
It seems that the token is incorrect for the web API which protected by Azure AD. Please check the aud claim in the token which should match the Audience you config in the web API project. You can check the aud claim by parse the token from this site.
And if you still have the problem please share the code how you protect the web API.
Update
If you were using the Express mode like below, you need to acquire the access_token using the app which you associate with the web API.
If you were using the Advanced mode, we should also use the that app to acquire the token and the ResourceUri should matched the value you config in ALLOWED TOKEN AUDIENCES like below:

Implement outlook calendar api in outlook add-in

I'm trying to implement the Office365 Outlook Calendar API inside the Outlook 365 Add-in. The Outlook Calendar API is fully implemented in the web application. Everything works fine with OAuth2 and the returned auth_token in the web application.
I'm having issues to sign in with OAuth2 inside the add-in. If you open the OAuth2-Login by Microsoft inside the add-in, it opens a Internet Explorer instance once you entered your appdev****#outlook[dot]com-account. This does not work with the auth_token saved in the session.
I tried to save the auth_token in a database (see //Test part) and request it for the user inside the add-in. This errors with a DataServiceClientException: Unauthorized
Unknown location.
[Route("SignIn")]
public async Task<ActionResult> SignIn()
{
string authority = "https://login.microsoftonline.com/common";
string clientId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
AuthenticationContext authContext = new AuthenticationContext(authority);
Uri redirectUri = new Uri(Url.Action("Authorize", "outlook", null, HttpContext.Request.Scheme));
Uri authUri = await authContext.GetAuthorizationRequestUrlAsync(scopes, null, clientId,
redirectUri, UserIdentifier.AnyUser, null);
return Redirect(authUri.ToString());
}
[Route("Authorize")]
public async Task<ActionResult> Authorize()
{
string authCode = Request.Query["code"];
string authority = "https://login.microsoftonline.com/common";
string clientId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
string clientSecret = "xxxxxxxxxxxx";
AuthenticationContext authContext = new AuthenticationContext(authority);
Uri redirectUri = new Uri(Url.Action("Authorize", "outlook", null, HttpContext.Request.Scheme));
ClientCredential credential = new ClientCredential(clientId, clientSecret);
try
{
var authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(
authCode, redirectUri, credential, scopes);
HttpContext.Session.SetString("access_token", authResult.Token);
HttpContext.Session.SetString("user_email", GetUserEmail(authContext, clientId));
//*** TEST ***
_dbContext.ApplicationUsers.FirstOrDefault(e => e.Email == "appdev****#outlook.com").AccessToken = authResult.Token;
_dbContext.ApplicationUsers.FirstOrDefault(e => e.Email == "appdev****#outlook.com").Email = GetUserEmail(authContext, clientId);
return Content("Access Token: " + authResult.Token + " Email: " + GetUserEmail(authContext, clientId));
}
catch (AdalException ex)
{
return Content(string.Format("ERROR retrieving token: {0}", ex.Message));
}
}
New answer
This is a common problem with Office add-in new generation (formerly App for Office) and OAUTH authentication. The fact that the add-in runs in a sandboxed iFrame force the authentication to be made in a popup window. There are also some problems to retrieve the auth token in the parent (sandboxed iFrame) window because frame communications are forbidden in this context.
I proposed a solution here but the best solution comes from Richard DiZerega and is proposed here.
From what I have understood, you try to save the auth_token in a database so it will be requested by the iFrame add-in later on. It is closed to what Richard DiZerega proposes.
Old mistaken answer
You are facing this issue because you probably registered you Azure AD app as a web application. Now you are requesting it with a native client without any 'url location' that is why this is failing.
There is a different authentication scenario for native client.
I think this is no big deal just register another app in your Azure AD for native client (this is the first question asked when you create an app).

How to validate the access token I'm receiving from WPF client in Web API?

I'm building two applications, WPF and Web API.
WPF connects to an identity server (now it's Azure AD) and get the access token then send it to my Web API to get the data.
How can I, in Web API, validate the access token to make sure it's correct. ?
Now I'm using Azure as I said but I should build to be able to validate any access token from any identity provider.
Is there an example or article explain this ?
Thanks
Encountered the same probelem.
I decided to use JWTToken
My architecture is the next one
Front <-> WebApi <-> Database
Front is in MVC4 WebApi2
The front will use a FormAuthentication method.
Save the JWT token once the user is fully logged, then send the Authentication Header on each request I do to the webapi.
The front part will only carry the encrypted JWT Token, nothing will be decrypted from it. Only send the token into the authentication http header tag.
On the webapi side each request are cached into DelegatingHandle, Check is the called method need to be authorized or not, validate the JWTToken then do what ever the webapi method does.
I cannot send you some part of my code because this is bellongs now to my company, but I can link you some Internet readings :)
1 - JWT
2- ASP.Net Web API with JWT (webapi handler)
You must then use the [Authorize] or [AllowAnonymous] tags on your webapi methods.
You can even create your own tag to handle all the Groups things.
If you have more questions, feel free to ask :)
I think this will answer to 99% of your security Questions.
I know this is one year old question, but I hope this answer will help other users :)
Client Side code
public async void Authenticate(string aadInstance, string tenant, string clientId, Uri redirectUri, string resourceId)
{
try
{
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
authContext = new AuthenticationContext(authority, new FileCache());
AuthenticationResult result = null;
try
{
result = await authContext.AcquireTokenSilentAsync (resourceId, clientId);
}
catch (AdalException ex)
{
if (ex.ErrorCode == AdalError.UserInteractionRequired || ex.ErrorCode == AdalError.FailedToAcquireTokenSilently)
{
result = await authContext.AcquireTokenAsync(resourceId, clientId, redirectUri, new PlatformParameters(PromptBehavior.Always));
}
}
ticket = result.AccessToken;
user = result.UserInfo.DisplayableId.Split('#')[0];
}
catch (Exception ex)
{
ticket = "Error";
throw ex;
}
}
Server side code
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
private JwtSecurityToken Validate(string token)
{
string stsDiscoveryEndpoint = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
IssuerSigningKeys = config.SigningKeys, //.net core calls it "IssuerSigningKeys" and "SigningKeys"
ValidateLifetime = true
};
JwtSecurityTokenHandler tokendHandler = new JwtSecurityTokenHandler();
SecurityToken jwt;
var result = tokendHandler.ValidateToken(token, validationParameters, out jwt);
return jwt as JwtSecurityToken;
}

Google+ Moments.insert - Unauthorized Error

According to the docuemtntation for Moments.insert with the Google+ API autentication with the following scope is required
https://www.googleapis.com/auth/plus.login
I am authenticating with all of the possible PlusService scopes but i am still getting the following error
Google.Apis.Requests.RequestError Unauthorized [401] Errors [
Message[Unauthorized] Location[ - ] Reason[unauthorized]
Domain[global]
//Scopes for use with Google+ API
// activating Google+ API in console
// Documentation: https://developers.google.com/+/api/oauth
string[] scopes = new string[] {
PlusService.Scope.PlusLogin,
PlusService.Scope.UserinfoEmail,
PlusService.Scope.UserinfoProfile
};
string _client_id = "2046123799103-d0vpdthl4ms0soutcrpe036ckqn7rfpn.apps.googleusercontent.com";
string _client_secret = "NDmluNfTgUk6wgmy7cFo64RV";
PlusService service = null;
UserCredential credential = null;
try {
// here is where we Request the user to give us access, or use the Refresh Token that was previously stored in %AppData%
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets {
ClientId = _client_id, ClientSecret = _client_secret
},
scopes,
Environment.UserName,
CancellationToken.None,
new FileDataStore("Daimto.GooglePlus.Auth.Store")).Result;
} catch (Exception ex) {
//If the user hits cancel you wont get access.
if (ex.InnerException.Message.IndexOf("access_denied") != -1) {
Console.WriteLine("User declined access");
Console.ReadLine();
return;
} else {
Console.WriteLine("Unknown Authentication Error:" + ex.Message);
Console.ReadLine();
return;
}
}
// Now we create a Google service. All of our requests will be run though this.
service = new PlusService(new BaseClientService.Initializer() {
HttpClientInitializer = credential,
ApplicationName = "Google Plus Sample",
});
Moment body = new Moment();
body.Type = "http://schema.org/AddAction";
ItemScope itemScope = new ItemScope();
itemScope.Id = "target-id-1";
itemScope.Type = "http://schema.org/AddAction";
itemScope.Name = "The Google+ Platform";
itemScope.Description = "A page that describes just how awesome Google+ is!";
itemScope.Image = "https://developers.google.com/+/plugins/snippet/examples/thing.png";
body.Object = itemScope;
try {
var l = service.Moments.Insert(body, "me", MomentsResource.InsertRequest.CollectionEnum.Vault);
l.Execute();
} catch (Exception ex) {
int i = 1;
}
I have tested authentication and it is working i am able to list activities and other things. Its only inserting moments that is giving me this error. I have also tried doing this in PHP and am getting the same error. What am I missing?
Update: I found something in the documentation for moments.insert
When authenticating for moments.insert, you must include the
data-requestvisibleactions parameter to specify which types of App
Activities your application will write.
I have not figured out yet how to set this data-requestvisibleactions.
As you've noticed, you need to add the request_visible_actions parameter to the request. Most of the other OAuth libraries from Google have added a shortcut to do this, but it looks like the .NET library hasn't. For example, the PHP library has setRequestVisibleActions().
Internally, the convenience method GoogleWebAuthorizationBroker.AuthorizeAsync() calls AuthorizationCodeFlow.CreateAuthorizationCodeRequest() to generate the URL used in the call. You can subclass AuthorizationCodeFlow and AuthorizationCodeRequestUrl (which it returns) to add the parameter in question and then go through the flow more directly.
You can see an example of how to do this at https://github.com/gguuss/google-dotnet-demo/blob/master/AuthWithAppActivities/Program.cs
You can also use a Google+ Sign-In button to do the initial auth flow and pass the one-time code to the server, which can then proceed to turn this into a valid token. Since the Sign-In button has the data-requestvisibleactions attribute, this will take care of that part of the auth for you.

Categories