Implement outlook calendar api in outlook add-in - c#

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).

Related

OAuth 2.0 With Desktop App. No Microsoft login screen opened

I tried to call a test connection using C#. At the beginning it worked, a few days later, I have deleted the token and tried again => the user authentication / Microsoft login window does not open anymore.
No matter if the settings were wrong or right, the window always opened.
(it did not work even on a completely rebuilt PC)
Problem:
When executing the method "await app.AcquireTokenInteractive(scopes).ExecuteAsync();" It looks like the app is waiting for input in the microsoft login window, but no window opens. Unfortunately there is no response.
My Azure App Configuration:
I registered my app in the Azure portal as "Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft account".
1
RedirectUri
For.NET Desktop i used: https://login.microsoftonline.com/common/oauth2/nativeclient
2
In the API permissions i added following permission scopes:
offline_access
email
IMAP.AccessAsUser.All
POP.AccessAsUser.All
SMTP.Send
3
In the line var result = await app.AcquireTokenInteractive(scopes).ExecuteAsync(); it should open the window.
My C# Code:
var app = PublicClientApplicationBuilder
.Create(accessParameters.ClientId)
.WithAuthority(
AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount
)
.WithDefaultRedirectUri()
.Build();
TokenCacheHelper.EnableSerialization(app.UserTokenCache);
var scopes = new string[]
{
"offline_access",
"email",
"https://outlook.office.com/IMAP.AccessAsUser.All",
"https://outlook.office.com/POP.AccessAsUser.All",
"https://outlook.office.com/SMTP.Send",
};
string userName;
string accessToken;
var account = (await app.GetAccountsAsync()).FirstOrDefault();
try
{
AuthenticationResult refresh = await app
.AcquireTokenSilent(scopes, account)
.ExecuteAsync();
userName = refresh.Account.Username;
accessToken = refresh.AccessToken;
}
catch (MsalUiRequiredException e)
{
var result = await app.AcquireTokenInteractive(scopes)
.ExecuteAsync();
userName = result.Account.Username;
accessToken = result.AccessToken;
}
string[] acc = { userName, accessToken };
return acc;
It should look like this

AADSTS501051: Application '{API GUID}'(DEV-API) is not assigned to a role for the application '{API GUID}'(DEV-API)

I want to access one API by its Client Credential directly not via any web application
private async Task<string> GetAutheticationToken(string APITypeSelected, string APIKeySelected=null)
{
string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
string tenant = ConfigurationManager.AppSettings["ida:AADTenant"];
string appKey = ConfigurationManager.AppSettings[APIKeySelected];
string apiID = ConfigurationManager.AppSettings[APITypeSelected];
//appKey = HttpUtility.UrlEncode(appKey);
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
using (HttpClient client = new HttpClient())
{
Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext authContext = null;
ClientCredential clientCredential = null;
authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(authority);
//encodeURIComponent(client_secret);
clientCredential = new ClientCredential(apiID, appKey);
AuthenticationResult authResult = null;
authResult = await authContext.AcquireTokenAsync(apiID, clientCredential);
return authResult.AccessToken;
}
}
while executing I am getting bellow error(AADSTS501051) in this line
authResult = await authContext.AcquireTokenAsync(apiID, clientCredential);
AADSTS501051: Application '{API GUID}'(DEV-API) is not assigned to a
role for the application '{API GUID}'(DEV-API).
Do I have to give API permission to itself.
What I need to do.
Thanks,
First you need to make a user role for application if app assignment is required. if not there is no problem. If app assignment is required, Go back to api permission and in my api give permission for the created role, see Microsoft documentation url
https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-protected-web-api-app-registration
Ahh so you want an access token to the API itself? Not sure if that's possible..
If this in another app, it should be registered as another app in Azure AD.
It can then require application permissions on the API and call it via client credentials.
You can see how to define permissions here: https://joonasw.net/view/defining-permissions-and-roles-in-aad
If this is within the same app, it sounds odd that it would acquire a token for itself.
This error message indicates that you need to add an "App role" to your app registration. You can do so by first adding a new App role on {API GUID}
and then assign the app {API GUID} this role (don't forget to give admin consent)
Essentially what is happening here is that your app registration {API GUID} got a role on {API GUID} to create access tokens for the audience {API GUID}, so: itself.
When you use "authContext.AcquireTokenAsync(apiID, clientCredential);" to get the access token, you need to use identifierUri of your ad application as resource.
For example:
string tenantId = "your tenant id or name, for example: hanxia.onmicrosoft.com";
string clientId = "your client id";
string resource = "the identifierUri of your ad application ";
string clientSecret = "";
ClientCredentia clientCredentia = new ClientCredentia(clientId,clientSecret);
var context = new AuthenticationContext("https://login.microsoftonline.com/" + tenantId);
AuthenticationResult result = context.AcquireTokenAsync(resource, clientCredentia);
For more details, please refer to the document.

Problem with local development for google-oauth / calendar api

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

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

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)

Categories