I'm getting the error AADSTS70002: Error validating credentials. AADSTS50012: Client assertion audience claim does not match Realm issuer
when running this code.
string[] scopes = new string[]{"https://graph.microsoft.com/.default"};
var certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
certStore.Open(OpenFlags.ReadOnly);
var cert = certStore.Certificates.Cast<X509Certificate2>().First(c => c.Thumbprint == "XXX-XXX etc");
var cas = new ClientAssertionCertificate(cert);
var cc = new Microsoft.Identity.Client.ClientCredential(cas);
var client = new Microsoft.Identity.Client.ConfidentialClientApplication("XX-XXX etc", "http://localhost", cc, new TokenCache(), new TokenCache() );
var authResult = await client.AcquireTokenForClientAsync(scopes);
var dap = new DelegateAuthenticationProvider(rm =>
{
rm.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", authResult.AccessToken);
return Task.FromResult(0);
});
var gClient = new GraphServiceClient(dap);
gClient.Me.Dump();
Error occurrs on the call to AcquireTokenForClientAsync() method.
I can not find any online documentation for MSAL and Daemon clients where no user authentication is possible.
Suggestions ?
Found the problem. I needed to use the second overload of the ConfidentialClientApplication constructor, and supply the authorisation like this.
string authorityFormat = "https://login.microsoftonline.com/{0}/v2.0";
string tennantId = "xxx-xx-xx";
then
var client = new Microsoft.Identity.Client.ConfidentialClientApplication("xxx-x-xx etc", string.Format(authorityFormat, tennantId), "http://localhost", cc, new TokenCache(), new TokenCache() );
The code Here pointed me in the right direction.
Related
I am trying to create a channel using MS Graph within a BotFramework bot. I get what appears to be a valid access Token. However the code below generates the following error:
The collection type 'Microsoft.Graph.IChannelMembersCollectionPage' on 'Microsoft.Graph.Channel.Members' is not supported.
var credential = new DefaultAzureCredential();
var token = credential.GetToken(
new Azure.Core.TokenRequestContext(
new[] { "https://graph.microsoft.com/.default" }));
var accessToken = token.Token;
Logger.LogWarning($"Token:{accessToken.ToString()}");
var graphServiceClient = new GraphServiceClient(
new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage
.Headers
.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
return Task.CompletedTask;
}));
try
{
var chan = new Channel
{
DisplayName = $"Chan1",
Description = "This channel is where we debate all future world domination plans",
MembershipType = ChannelMembershipType.Standard
};
await graphServiceClient.Teams["{GroupID}"].Channels.Request().AddAsync(chan);
}
You can use Graph SDK to generate token internally. Please try providing application permissions in azure portal and use the below code to create a channel in MS Teams. Below are the packages you need to install.
This is an example for application permissions. You can try the same code with minor changes/to no changes for delegate permissions.
string clientId = "";
string clientSecret = "1";
string tenantId = "";
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret) // or .WithCertificate(certificate)
.Build();
//AuthorizationCodeProvider authProvider = new AuthorizationCodeProvider(confidentialClientApplication, scopes);
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var channel = new Channel
{
DisplayName = "Topic Discussion",
Description = "This channel is where we debate all future architecture plans",
MembershipType = ChannelMembershipType.Standard
};
await graphClient.Teams["{Your-teams-id}"].Channels
.Request()
.AddAsync(channel);
I am having trouble sending an email from a daemon app. I can get the token with the Client Credential flow but I am unable to send an email with the Microsoft Graph API. I am getting the following error:
Code: BadRequest
Message: Found a function 'microsoft.graph.sendMail' on an open property. Functions on open properties are not supported.
Inner error:
AdditionalData:
request-id: e2e3bb60-2212-4c99-8858-d109aaf4f1cd
date: 2020-01-30T11:18:21
ClientRequestId: e2e3bb60-2212-4c99-8858-d109aaf4f1cd
}
Below is the coding for sending an email through Microsoft Graph.
private readonly IClientCredentialProvider _clientCredentialProvider;
public MailTransmitter()
{
AuthenticationConfig config = AuthenticationConfig.ReadFromJsonFile("appsettings.json"); // contains the tenantId, clientSecret and clientId
_clientCredentialProvider = new ClientCredentialProvider(config);
}
public async Task<bool> SendMail(List<UserEntitlement> sortedListByLastAccessDate)
{
//GraphServiceClient graphClient = new GraphServiceClient(_clientCredentialProvider.GetAuthorizationCodeProvider());
var result = await _clientCredentialProvider.GetClientToken(); // Get token using Client Credentials flow
var accessToken = result.AccessToken;
//should I pass the URL to the graphServiceClient like below? Is the URL right?
var graphServiceClient = new GraphServiceClient("https://graph.microsoft.com/v1.0/0a181b4b-a2fb-4e38-b23b-2c72adc882f2/users/c26d8491-82f8-4f08-990e-35a73ad61ede/memberOf", new DelegateAuthenticationProvider(async (requestMessage) => {
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
}));
var message = new Message
{
Subject = "Meet for lunch?",
Body = new ItemBody
{
ContentType = BodyType.Text,
Content = "The new cafeteria is open."
},
ToRecipients = new List<Recipient>()
{
new Recipient
{
EmailAddress = new EmailAddress
{
Address = "Bla#hotmail.com"
}
}
},
From = new Recipient
{
EmailAddress = new EmailAddress {
Address = "bla.bla#test.nl"
}
}
};
var saveToSentItems = false;
//Error occurs here
await graphServiceClient.Users["c26d8491-82f8-4f08-990e-35a73ad61ede"]
.SendMail(message, saveToSentItems)
.Request()
.PostAsync();
return true;
}
In case you are wondering how I used the client credentials flow, take a look at: https://github.com/Azure-Samples/active-directory-dotnetcore-daemon-v2/tree/master/1-Call-MSGraph/daemon-console.
What is the problem exactly?
Thanks in advance!
The provided error occurs since invalid url is provided for GraphServiceClient, it expects the first argument to be service root url:
public GraphServiceClient(string baseUrl,IAuthenticationProvider authenticationProvider,IHttpProvider httpProvider = null)
in case of Microsoft Graph API, service root url consist of:
https://graph.microsoft.com is the Microsoft Graph API endpoint.
{version} is the target service version, for example, v1.0 or beta.
for instance, https://graph.microsoft.com/v1.0. Refer Calling the Microsoft Graph API for a details
Example
var app = ConfidentialClientApplicationBuilder.Create(clientId)
.WithClientSecret(clientSecret)
.WithAuthority(new Uri(authority))
.Build();
var scopes = new string[] {"https://graph.microsoft.com/.default"};
var result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
var client = new GraphServiceClient(
"https://graph.microsoft.com/v1.0",
new DelegateAuthenticationProvider(async (requestMessage) =>
{
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", result.AccessToken);
}));
or via Client credentials provider for that matter:
var app = ConfidentialClientApplicationBuilder.Create(clientId)
.WithClientSecret(clientSecret)
.WithAuthority(new Uri(authority))
.Build();
var authProvider = new ClientCredentialProvider(app);
var client = new GraphServiceClient(authProvider);
Prerequisites: requires Microsoft.Graph.Auth package
I'm trying to create S/MIME email with MimeKit in the .NET Core App. It's working fine with local certificate like this:
var signer = new CmsSigner("cert.pfx", "password");
message.Body = MultipartSigned.Create(ctx, signer, message.Body);
To make app more secure, I've uploaded my certificate to the Azure KeyVault. And here's my problems has begun.
The idea is to use only certificate from Azure KeyVault without any passwords etc (so only store reference to the KeyVault instead of storing two links (KeyVault cert + KeyVault certPass)).
Here's how I'm trying to get certificate from the Azure KeyVault.
private static async Task<X509Certificate2> GetCertificate()
{
var tokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(tokenProvider.KeyVaultTokenCallback));
var azureKeyVaultCert = await keyVaultClient.GetSecretAsync("https://<myapp>.vault.azure.net/secrets/<secretName>/<secretId>");
var certBytes = Convert.FromBase64String(azureKeyVaultCert.Value);
var cert = new X509Certificate2(certBytes);
return cert;
}
And then:
var cert = new GetCertificate();
var signer = new CmsSigner(cert);
Here I got error like this: MimeKit: 'RSACng' is currently not supported.
After that I tried another approach. It's working fine, however, I need to use my password. I don't think this is good idea (despite the fact I can also use Azure KeyVault for cert password).
private static async Task<byte[]> GetCertificateBytes()
{
var tokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(tokenProvider.KeyVaultTokenCallback));
var azureKeyVaultCert = await keyVaultClient.GetSecretAsync("https://<myapp>.vault.azure.net/secrets/<secretName>/<secretId>");
var certBytes = Convert.FromBase64String(azureKeyVaultCert.Value);
var certCollection = new X509Certificate2Collection();
certCollection.Import(certBytes, null, X509KeyStorageFlags.Exportable);
return certCollection.Export(X509ContentType.Pkcs12, "password");
}
var certBytes = await GetCertificateBytes();
using(var certStream = new System.IO.MemoryStream(certBytes))
{
var signer = new CmsSigner(certStream, "password");
}
In case when I use await keyVaultClient.GetCertificateAsync(), I got an error that there's no private key.
Also, there's difference between certificates created via new X509Certificate2() and X509Certificate2Collection.Export().
Maybe I miss something?
I'm experiencing an intermittent problem with our SharePoint 2010 REST API. I have a .Net Core Console application that makes a series of calls to SharePoint List Endpoints to get a JSON response. My problem is that at random times, the API response is an error page:
A relative URI cannot be created because the 'uriString' parameter
represents an absolute URI.http://www.example.com/somefolder/file.svc
Is there a problem with my HTTPClient configuration? Is there a configuration setting that I can toggle in SharePoint to prevent the error or more reliable?
var uri = new Uri("http://www.example.com/");
var credential = new NetworkCredential("username", "password", "domain");
var credentialsCache = new CredentialCache { { uri, "NTLM", credential } };
var handler = new HttpClientHandler { Credentials = credentialsCache };
HttpClient Client = new HttpClient(handler);
Client.BaseAddress = new Uri("http://www.example.com/sharepoint/path/ListData.svc/");
// Make the list request
var result = await Client.GetAsync("MySharePointList");
To get the list items, the REST API URI like below.
http://sp2010/_vti_bin/ListData.svc/listname
Modify the code as below.
var siteUrl = "http://www.example.com/";
var listName = "MySharePointList";
var uri = new Uri(siteUrl);
var credential = new NetworkCredential("username", "password", "domain");
var credentialsCache = new CredentialCache { { uri, "NTLM", credential } };
var handler = new HttpClientHandler { Credentials = credentialsCache };
HttpClient client = new HttpClient(handler);
client.BaseAddress = new Uri(uri, "/_vti_bin/ListData.svc");
client.DefaultRequestHeaders.Add("Accept", "application/json;odata=verbose");
client.DefaultRequestHeaders.Add("ContentType", "application/json;odata=verbose");
var requestURL = siteUrl + "/_vti_bin/ListData.svc/" + listName;
// Make the list request
var result = client.GetAsync(requestURL).Result;
var items= result.Content.ReadAsStringAsync();
I'm trying to call Send on the GmailService from a C# .NET MVC app. and I keep getting a 403 error when I call send.
I've checked my scopes, the Gmail setup definitely has the Gmail API enabled, and my ClientID and ClientSecret are fresh.
var httpClient = new HttpClient{
BaseAddress = new Uri("https://www.googleapis.com")
};
var requestUrl = $"oauth2/v4/token?code={code}&client_id={ClientId}&client_secret={SecretKey}&redirect_uri={RedirectUrl}&grant_type=authorization_code";
var dict = new Dictionary<string, string>{
{ "Content-Type", "application/x-www-form-urlencoded" }
};
var req = new HttpRequestMessage(HttpMethod.Post, requestUrl){Content = new FormUrlEncodedContent(dict)};
var response = await httpClient.SendAsync(req);
var token = JsonConvert.DeserializeObject<GmailToken>(await response.Content.ReadAsStringAsync());
Session["user"] = token.AccessToken;
//var obj = await GetuserProfile(token.AccessToken);
var obj = await DoSendEmail(token);
public void DoSendEmail(GmailToken inToken) {
const string fromAcct = "XXXXXXXX#gmail.com";
TokenResponse token = new TokenResponse();
token.AccessToken = inToken.AccessToken;
token.ExpiresInSeconds = inToken.ExpiresIn;
token.IdToken = inToken.IdToken;
token.TokenType = inToken.TokenType;
token.IssuedUtc = DateTime.UtcNow;
IAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer {
ClientSecrets = secrets,
Scopes = SCOPES,
ProjectId = "Xcent CP"
});
UserCredential credential = new UserCredential(flow, fromAcct, token);
if (credential.Token.IsExpired(credential.Flow.Clock)) {
bool success = credential.RefreshTokenAsync(CancellationToken.None).Result;
if (!success) {
throw new Exception("Could not refresh token");
}
}
GmailService gs = null;
try {
gs = new GmailService(new Google.Apis.Services.BaseClientService.Initializer() {
ApplicationName = APP_NAME,
HttpClientInitializer = credential
});
var mailMessage = new System.Net.Mail.MailMessage();
mailMessage.From = new System.Net.Mail.MailAddress(fromAcct);
mailMessage.To.Add("XXXXXXXX#comcast.net");
mailMessage.ReplyToList.Add(fromAcct);
mailMessage.Subject = "Test email";
mailMessage.Body = "<html><body>Hi <b>Lee</b>, this is <b>yet another</b> test message.</body></html>";
mailMessage.IsBodyHtml = true;
var mimeMessage = MimeKit.MimeMessage.CreateFromMailMessage(mailMessage);
var gmailMessage = new Google.Apis.Gmail.v1.Data.Message {
Raw = Encode(mimeMessage.ToString())
};
gs.Users.Messages.Send(gmailMessage, fromAcct).Execute();
}
catch (Exception ex) {
throw ex;
}
finally {
if (gs != null) {
gs.Dispose();
}
gs = null;
}
}
I'm not sure where to look...I've been through many many many online articles and tutorials, tried seemingly everything, and I'm still stuck with the 403 error. Help!
Thanks,
Lee
So after many hours spent looking at this I figured out the problem. My link to the Google login was this:
Response.Redirect($"https://accounts.google.com/o/oauth2/v2/auth?client_id={ClientId}&response_type=code&scope=openid%20email%20profile&redirect_uri={RedirectUrl}&state=abcdef");
"openid%20email%20profile" was the only scope I was specifying for the login, hence the 403 error about the scope I was using for the flow variable.
phew!