I get the following error when I try to retrieve AccessToken.
/MicrosoftGraph/Authorise?error=access_denied&error_description=AADSTS65005%3a+The+client+application+has+requested+access+to+resource+%27https%3a%2f%2fgraph.microsoft.com%2f%27.+This+request+has+failed+because+the+client+has+not+specified+this+resource+in+its+requiredResourceAccess+list.%0d%0aTrace+ID%3a+7cd46ad3-d294-41ad-98ec-6ef06db7a0db%0d%0aCorrelation+ID%3a+4e2a6d3b-b3dd-4a98-b36d-550d8f8c3382%0d%0aTimestamp%3a+2016-01-27+10%3a40%3a12Z
Which is... graph.microsoft.com This request has failed because the client has not specified this resource in its requiredResourceAccess list
It is a multitenant application in Azure Active Directory. I am able to login successfully with one of my email Ids but not with another one.
Where am I going wrong? What am I missing?
Code Snippet:
public ActionResult Login() {
....
Uri authUri = authContext.GetAuthorizationRequestURL(
MicrosoftGraphSettings.O365UnifiedAPIResource,
MicrosoftGraphSettings.ClientId,
loginRedirectUri,
UserIdentifier.AnyUser,
null);
string authUriAsString = authUri.ToString();
return Redirect(authUriAsString);
}
public async Task<ActionResult> Authorise()
{
Uri loginRedirectUri = new Uri(Url.Action("Authorise", "MicrosoftGraph", null, Request.Url.Scheme));
var authContext = new AuthenticationContext(MicrosoftGraphSettings.AzureADAuthority);
var authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(
Request.Params["code"],
loginRedirectUri,
new ClientCredential(MicrosoftGraphSettings.ClientId, MicrosoftGraphSettings.ClientSecret),
MicrosoftGraphSettings.O365UnifiedAPIResource);
}
I have tried the following
public static string O365UnifiedAPIResource = #"https://graph.microsoft.com/";
//public static string O365UnifiedAPIResource = #"https://graph.windows.net/";
Using the second one, the authentication succeeds but whenever I use my existing code to access list of files in OneDrive for business account or create a text file, it throws an Unauthorized exception while making an API call.
That error message indicates that your application doesn't have delegated permissions for the "Microsoft Graph" (https://graph.microsoft.com/) resource.
Please use the Azure Management Portal (https://manage.windowsazure.com) to configure delegated permissions for that resource. Find the app -> Configure -> "permissions to other applications" -> "Add application" -> select "Microsoft Graph".
Since you're able to get a token for "https://graph.windows.net/" your application already has permissions configured for the "Windows Azure Active Directory" resource, but that is a different resource than "Microsoft Graph".
Related
My web service is currently doing basic username/password authentication in order to subscribe the exchange user for receiving the events (like new mail event etc) like below:
var service = new ExchangeService(exchangeVersion)
{
KeepAlive = true,
Url = new Uri("some autodiscovery url"),
Credentials = new NetworkCredential(username, password)
};
var subscription = service.SubscribeToPushNotifications(
new[] { inboxFolderFoldeID },
new Uri("some post back url"),
15,
null,
EventType.NewMail,
EventType.Created,
EventType.Deleted,
EventType.Modified,
EventType.Moved,
EventType.Copied);
Now, I am supposed to replace the authentication mechanism to use OAuth protocol. I saw some examples but all of them seem to be talking about authenticating the client (https://msdn.microsoft.com/en-us/library/office/dn903761%28v=exchg.150%29.aspx?f=255&MSPPError=-2147217396) but nowhere I was able to find an example of how to authenticate an exchange user with OAuth protocol. Any code sample will help a lot. Thanks.
It's not clear what you mean with 'web service' and how you currently get the username and password. If that is some kind of website where the user needs to login or pass credentials, then you'll have to start an OAuth2 grant from the browser as in redirecting the clients browser to the authorize endpoint to start implicit grant or code grant. The user will be presented a login screen on the OAuth2 server (and not in your application), once the user logs in a code or access token (depending on the grant) will be returned to your application which you can use in the ExchangeService constructor.
If that 'web' service is some service that runs on the users computer you can use one of the methods described below.
Get AccessToken using AuthenticationContext
The example seems to be based on an older version of the AuthenticationContext class.
The other version seems to be newer, also the AcquireToken is now renamed to AcquireTokenAsync / AcquireTokenSilentAsync.
No matter which version you're using, you will not be able to pass username and password like you're doing in your current code. However, you can let the AcquireToken[Async] method prompt for credentials to the user. Which, let's be honest, is more secure then letting your application deal with those user secrets directly. Before you know, you'll be storing plain text passwords in a database (hope you aren't already).
In both versions, those methods have a lot of overloads all with different parameters and slightly different functionality. For your use-case I think these are interesting:
New: AcquireTokenAsync(string, string, Uri, IPlatformParameters) where IPlatformParameters could be new PlatformParameters(PromptBehavior.Auto)
Old: AcquireToken(string, string, Uri, PromptBehavior where prompt behavior could be PromptBehavior.Auto
Prompt behavior auto, in both vesions, means: the user will be asked for credentials when they're not already cached. Both AuthenticationContext constructors allow you to pass a token-cache which is something you can implement yourself f.e. to cache tokens in memory, file or database (see this article for an example file cache implementation).
Get AccessToken manually
If you really want to pass in the user credentials from code without prompting the user, there is always a way around. In this case you'll have to implement the Resource Owner Password Credentials grant as outlined in OAuth2 specificatioin / RFC6749.
Coincidence or not, I have an open-source library called oauth2-client-handler that implements this for use with HttpClient, but anyway, if you want to go this route you can dig into that code, especially starting from this method.
Use Access Token
Once you have an access token, you can proceed with the samples on this MSDN page, f.e.:
var service = new ExchangeService(exchangeVersion)
{
KeepAlive = true,
Url = new Uri("some autodiscovery url"),
Credentials = new OAuthCredentials(authenticationResult.AccessToken))
};
In case someone is still struggling to get it to work. We need to upload a certificate manifest on azure portal for the application and then use the same certificate to authenticate the client for getting the access token. For more details please see: https://blogs.msdn.microsoft.com/exchangedev/2015/01/21/building-daemon-or-service-apps-with-office-365-mail-calendar-and-contacts-apis-oauth2-client-credential-flow/
Using the example code in this Microsoft Document as the starting point and these libraries:
Microsoft Identity Client 4.27
EWS Managed API v2.2
I am able to successfully authenticate and connect with Exchange on Office 365.
public void Connect_OAuth()
{
var cca = ConfidentialClientApplicationBuilder
.Create ( ConfigurationManager.AppSettings[ "appId" ] )
.WithClientSecret( ConfigurationManager.AppSettings[ "clientSecret" ] )
.WithTenantId ( ConfigurationManager.AppSettings[ "tenantId" ] )
.Build();
var ewsScopes = new string[] { "https://outlook.office365.com/.default" };
AuthenticationResult authResult = null;
try
{
authResult = cca.AcquireTokenForClient( ewsScopes ).ExecuteAsync().Result;
}
catch( Exception ex )
{
Console.WriteLine( "Error: " + ex );
}
try
{
var ewsClient = new ExchangeService();
ewsClient.Url = new Uri( "https://outlook.office365.com/EWS/Exchange.asmx" );
ewsClient.Credentials = new OAuthCredentials( authResult.AccessToken );
ewsClient.ImpersonatedUserId = new ImpersonatedUserId( ConnectingIdType.SmtpAddress, "ccc#pppsystems.co.uk" );
ewsClient.HttpHeaders.Add( "X-AnchorMailbox", "ccc#pppsystems.co.uk" );
var folders = ewsClient.FindFolders( WellKnownFolderName.MsgFolderRoot, new FolderView( 10 ) );
foreach( var folder in folders )
{
Console.WriteLine( "" + folder.DisplayName );
}
}
catch( Exception ex )
{
Console.WriteLine( "Error: " + ex );
}
}
The Microsoft example code did not work - the async call to AcquireTokenForClient never returned.
By calling AcquireTokenForClient in a separate try catch block catching a general Exception, removing the await and using .Result, this now works - nothing else was changed.
I realise that this is not best practice but, both with and without the debugger, the async call in the original code never returned.
In the Azure set-up:
A client secret text string was used - a x509 certificate was not necessary
The configuration was 'app-only authentication'
Hope this helps someone avoid hours of frustration.
I'm facing a problem with GMAIL API from Windows Azure (ASP.NET MVC).
From Local or Console App everything works fine, when I move it to my cloud service I get an "Access is denied" error.
Obviously on the Google Cloud Platform console I have both "localhost" and "myurl" in order to make it work.
This is my code:
UserCredential credential;
using (var stream = new FileStream(diskPath + "/Content/client_secret.json", FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None
);
}
var service = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
var resp = await service.HttpClient.GetStringAsync("https://www.googleapis.com/gmail/v1/users/me/messages?q=\"has:attachment\"");
I get the error on
await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None
);
I've the last package from NUGET for google API OAuth (https://www.nuget.org/packages/Google.Apis.Oauth2.v2).
I've seen online somebady referring to some access_type=offline parameter but I can't find any "completely working" example.
Why I get this huge difference between local and Azure?
Based from this documentation, you may receive an Access is denied error message when you try to debug a Web application in Visual Studio .NET, and you have administrative permissions.
The problem occurs when the following conditions are true:
You are logged on to your computer with administrative permissions.
You are debugging a Web application in Microsoft Visual Studio .NET.
The operating system that you are using is Microsoft Windows XP Service Pack 2.
The Microsoft ASP.NET worker process account is not a member of the Administrators group.
The problem occurs because the ASP.NET worker process does not have the Impersonate a client after authentication user right. If the worker process account does not have this right, the debugger cannot attach to the process. The worker process account is configured by using the processModel element in the Machine.config file.
You may also check the workaround given in this post. Check if you have added below tag to the web.config file inside system.webserver tag.
<defaultDocument>
<files>
<add value="Pages/Home.aspx"/>
</files>
</defaultDocument>
Finally I find out what was wrong with my solution!
Following this tutorial everything works fine:
https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#web-applications-aspnet-mvc
Summarizing:
Web applications (ASP.NET MVC)
Google APIs support OAuth 2.0 for Web Server Applications. In order to run the following code successfully, you must first add a redirect URI to your project in the Google API Console. Since you will use FlowMetadata and its default settings, set the redirect URI to your_site/AuthCallback/IndexAsync.
To find the redirect URIs for your OAuth 2.0 credentials, do the following:
Open the Credentials page in the API Console.
If you haven't done so already, create your OAuth 2.0 credentials by clicking Create credentials > OAuth client ID.
After you create your credentials, view or edit the redirect URLs by clicking the client ID (for a web application) in the OAuth 2.0 client IDs section.
After creating a new web application project in your IDE, add the right Google.Apis NuGet package for Drive, YouTube, or the other service you want to use. Then, add the Google.Apis.Auth.MVC package. The following code demonstrates an ASP.NET MVC application that queries a Google API service.
1) Add your own implementation of FlowMetadata.
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; }
}
}
FlowMetadata is an abstract class that contains your own logic for retrieving the user identifier and the IAuthorizationCodeFlow you are using.
In the above sample code a new GoogleAuthorizationCodeFlow is created with the right scopes, client secrets, and the data store. Consider adding your own implementation of IDataStore, for example you could write one that uses EntityFramework.
2) Implement your own controller that uses a Google API service. The following sample uses a DriveService:
public class HomeController : Controller
{
public async Task IndexAsync(CancellationToken cancellationToken)
{
var result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).
AuthorizeAsync(cancellationToken);
if (result.Credential != null)
{
var service = new DriveService(new BaseClientService.Initializer
{
HttpClientInitializer = result.Credential,
ApplicationName = "ASP.NET MVC Sample"
});
// YOUR CODE SHOULD BE HERE..
// SAMPLE CODE:
var list = await service.Files.List().ExecuteAsync();
ViewBag.Message = "FILE COUNT IS: " + list.Items.Count();
return View();
}
else
{
return new RedirectResult(result.RedirectUri);
}
}
}
3 Implement your own callback controller. The implementation should be something like this:
public class AuthCallbackController : Google.Apis.Auth.OAuth2.Mvc.Controllers.AuthCallbackController
{
protected override Google.Apis.Auth.OAuth2.Mvc.FlowMetadata FlowData
{
get { return new AppFlowMetadata(); }
}
}
This example is for Google Drive but if you want (as I did) to get it working for GMAIL API, you have just to switch between DriveService and GmailService
I am new to Azure Data Lake Analytics and am converting a C# batch job to use service to service authentication before submitting stored procedures to Azure Data Lake Analytics.
public void AuthenticateADLUser()
{
//Connect to ADL
// Service principal / appplication authentication with client secret / key
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
cTokenCreds = ApplicationTokenProvider.LoginSilentAsync(strDomain, strWebApp_clientId, strClientSecret).Result;
SetupClients(cTokenCreds, strSubscriptionID);
}
public static void SetupClients(ServiceClientCredentials tokenCreds, string subscriptionId)
{
_adlaClient = new DataLakeAnalyticsAccountManagementClient(tokenCreds);
_adlaClient.SubscriptionId = subscriptionId;
_adlaJobClient = new DataLakeAnalyticsJobManagementClient(tokenCreds);
_adlsFileSystemClient = new DataLakeStoreFileSystemManagementClient(tokenCreds);
}
Even though I have given it the correct ClientId the error comes back with a different ClientID in the error when I execute the following code:
var jobInfo = _adlaJobClient.Job.Create(_adlsAccountName, jobId, parameters);.
The error message is:
The client 'e83bb777-f3af-4526-ae34-f5461a5fde1c' with object id 'e83bb777-f3af-4526-ae34-f5461a5fde1c' does not have authorization to perform action 'Microsoft.Authorization/permissions/read' over scope '/subscriptions/a0fb08ca-a074-489c-bed0-....
Why is the ClientID different than the one I used in the code?
Is this a code issue or a permissions issue? I assume that it is code since the ClientID is not an authorized one that I created.
note: The SubscriptionId is correct.
I assumed you created an Azure Active Directory App and are you the client and domain IDs of this app. If not, you'll need that... If you do have that, then can you check if the App has permissions over your Data Lake Store: https://learn.microsoft.com/en-us/azure/data-lake-store/data-lake-store-authenticate-using-active-directory
Had exactly same symptoms. WebApp was created in AAD in portal originally to access Azure Data Lake Store and same code-snippet worked perfectly. When I decided to re-use same WebApp (clientid/secret) it failed with same error, even though I have given reader/contributor roles on sub/RG/ADLA to the App.
I think the reason is that WebApp underneath has a "service principal" object (thus error msg shows different object id) and ADLA uses it for some reason. Mine didn't have credentials set - empty result:
Get-AzureRmADSpCredential -objectid <object_id_from_error_msg>
Added new password as described here
New-AzureRmADSpCredential -objectid <object_id_from_error_msg> -password $password
Used the pwd as secret in LoginSilentAsync, clientId was left as before - WebApp clientId (not the principal object id shown in the error)
I wasn't able to find this principal info in portal, only PS.
I am attempting to transfer ownership from a Service Account created document to another user who resides within the same Google Apps account using the code below but am getting the following error
The resource body includes fields which are not directly writable. [403]
Errors [Message[The resource body includes fields which are not directly writable.] Location[ - ] Reason[fieldNotWritable] Domain[global]]
var service = GetService();
try
{
var permission = GetPermission(fileId, email);
permission.Role = "owner";
var updatePermission = service.Permissions.Update(permission, fileId, permission.Id);
updatePermission.TransferOwnership = true;
return updatePermission.Execute();
}
catch (Exception e)
{
Console.WriteLine("An error occurred: " + e.Message);
}
return null;
Commenting out // permission.Role = "owner"; returns the error below
The transferOwnership parameter must be enabled when the permission role is 'owner'. [403] Errors [Message[The transferOwnership parameter must be enabled when the permission role is 'owner'.] Location[transferOwnership - parameter] Reason[forbidden] Domain[global]]
Assigning any other permissions works fine. Therefore, is this a limitation of the Service Account not being able to transfer ownership to any other account that doesn't use the #gserviceaccount.com email address (i.e. our-project#appspot.gserviceaccount.com > email#domain.com)?
The email#domain.com email address has been created and is managed within Google Apps.
In the case, it is not achievable, any pointers on where to look next? We need multiple users to have the ability to create documents ad hoc and assign permissions and transfer ownership on the fly via the API.
Thanks
I have found the answer and am posting for anyone else who comes across this question.
You can not use the 'Service Account Key JSON file' as recommended by Google.
You need to use the p.12 certificate file for authentication.
The code to create a drive service for mimicking accounts is as follows.
public DriveService GetService(string certificatePath, string certificatePassword, string googleAppsEmailAccount, string emailAccountToMimic, bool allowWrite = true)
{
var certificate = new X509Certificate2(certificatePath, certificatePassword, X509KeyStorageFlags.Exportable);
var credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(googleAppsEmailAccount)
{
Scopes = new[] { allowWrite ? DriveService.Scope.Drive : DriveService.Scope.DriveReadonly },
User = emailAccountToMimic
}.FromCertificate(certificate));
// Create the service.
return new DriveService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName
});
}
You need to follow the steps listed here to delegate domain-wide authority to the service account.
Allow 5 to 10 minutes after completing step 4.
You can now create documents under the 'emailAccountToMimic' user which sets them to be the owner during creation.
I don't think it is possible to transfer the ownership from a non-ServiceAccount to a ServiceAccount, vice versa.
If you do that interactively, you will get the below error:
Typically, the document can be created and owned by the users and ownership transfer can be done using their own credentials. You will also have the option to impersonate as the owner if your Service Account is granted with the domain-wide delegation correctly.
Users are created in azure AD for a native application that i built. I would like users to use their windows live ID if they have one, otherwise I would create an AD account for them.
AD accounts are able to login, but whenever a windows live account tries to login I get the following error message
No service namespace named 'timetray' was found in the data store.
I'm not sure what this means, what is the service namespace, or where can i find the datastore.
The name for the service namespace that i am using is the resource id uri for an application that I provisioned in the Active Directory
private Uri redirectUri = new Uri("http://TimeTray");
private string clientId = "{{Client-ID}}";
private AuthenticationContext _authenticationContext;
private string domainName = "common";
private string resourceAppIdUri = "http://TimeTray";
private string resourceBaseAddress = "http://timetray.azurohosted.com/";
public void Authenticate(OnLoginComplete onLoginComplete)
{
CredManCache creds = new CredManCache();
_authenticationContext = new AuthenticationContext("https://login.windows.net/" + domainName, creds);
AuthenticationResult authenticationResult = _authenticationContext.AcquireToken(resourceAppIdUri, clientId, redirectUri);
// _authenticationContext.AcquireToken(
UserEntity user = new UserEntity();
user.NTUserName = authenticationResult.UserInfo.UserId;
user.SID = authenticationResult.UserInfo.UserId;
onLoginComplete(user);
}
I assume you are setting up ADAL with AAD.
When creating AuthenticationContext:
Instead of https://login.windows.net/" + domainName (common)
Try
https://login.windows.net/[Guid for the web api configured on your client in Azure AD]/FederationMetadata/2007-06/FederationMetadata.xml
In its data store, Azure will now look for service namespace [guid] instead of what is deferred from the login name "billy"#timetray.onmicrosoft.com.
See in MSDN sample
http://code.msdn.microsoft.com/AAL-Native-Application-to-fd648dcf#content
It states "Set the authority to https://login.windows.net/common to defer choosing the AAD domain till the user signs in."
Also, use http://www.nuget.org/packages/Microsoft.IdentityModel.Clients.ActiveDirectory/
I guess you also need to create users in Azure AD that are linked to existing Microsoft Accounts.