I'm trying to do the following:
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(APP_ID)
.WithTenantId(AzureConfiguration.TenantId)
.WithClientSecret(APP_SECRET)
.Build();
var authProvider = new ClientCredentialProvider(confidentialClientApplication);
var client = new GraphServiceClient(GRAPH_URL, authProvider);
subscription = await client.Subscriptions.Request().AddAsync(new Subscription
{
ChangeType = "updated",
NotificationUrl = notificationUrl,
Resource = $"/groups/{CONTENT_GROUP_ID}/drive/root",
ExpirationDateTime = DateTimeOffset.UtcNow.AddMinutes(SubscriptionLength),
ClientState = Guid.NewGuid().ToString()
});
Content group is a valid group. The application has Files.ReadWrite.All and Group.ReadWrite.All at the application level, and the Grant Admin for button has been clicked.
This used to work with HTTPClient and REST with login like this:
var authContext = new AuthenticationContext($"https://login.microsoftonline.com/{AzureConfiguration.TenantId}");
var creds = new ClientCredential(APP_ID, APP_SECRET);
var authResult = await authContext.AcquireTokenAsync("https://graph.microsoft.com/", creds);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
and then calling it like this: with a helper function that just posts the json:
var result = await Post<Subscription, Subscription>(client, $"{GRAPH_URL}/subscriptions", new Subscription
{
ChangeType = "updated",
NotificationUrl = $"{apiUrl}/v1/SharepointNotifications/Notify",
Resource = $"/groups/{CONTENT_GROUP_ID}/drive/root",
ExpirationDateTime = DateTimeOffset.UtcNow.AddMinutes(SubscriptionLength),
ClientState = Guid.NewGuid().ToString()
});
(This code used to work but failed at this refresh of the subscription.)
I'm at a loss as to what's different and causing it not to work. How do I get this going?
Related
[SOLVED, see the edits]
I am working in Linqpad 6, running a script that I made based on the following articles:
https://learn.microsoft.com/en-us/graph/api/user-post-users?view=graph-rest-1.0&tabs=csharp
https://learn.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=CS
Here is my script:
void Main()
{
Debug.WriteLine("yo");
UserCreator creator = new();
creator.CreateUser();
}
public class UserCreator
{
public async void CreateUser()
{
var scopes = new[] { "User.ReadWriteAll" };
// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
var tenantId = "<MY_TENANT_ID>";
// Value from app registration
var clientId = "<MY_APPLICATION_ID>";
var pca = PublicClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.Build();
// DelegateAuthenticationProvider is a simple auth provider implementation
// that allows you to define an async function to retrieve a token
// Alternatively, you can create a class that implements IAuthenticationProvider
// for more complex scenarios
var authProvider = new DelegateAuthenticationProvider(async (request) =>
{
// Use Microsoft.Identity.Client to retrieve token
var result = await pca.AcquireTokenByIntegratedWindowsAuth(scopes).ExecuteAsync();
request.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", result.AccessToken);
});
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var user = new User
{
AccountEnabled = true,
DisplayName = "John",
MailNickname = "John",
UserPrincipalName = "john#mail.com",
PasswordProfile = new PasswordProfile
{
ForceChangePasswordNextSignIn = true,
Password = "xWwvJ]6NMw+bWH-d"
}
};
await graphClient.Users
.Request()
.AddAsync(user);
}
}
I am trying to add a new user to an Azure AD B2C app, but the request is failing with an InnerException of:
"The system cannot contact a domain controller to service the authentication request."
I am suspecting I need more info for the script, such as the name of the registered App, but I cannot find anything about it in the documentation. I find it likely that the request is not returning the correct auth token.
Below is a screenshot of the error:
Updated code
This is my final, working result. Originally, I tried to create a user through my own account, but MFA got in the way. The actual way to do it, is through an app registration.
void Main()
{
UserCreator creator = new();
creator.CreateUser();
}
public class UserCreator
{
public async void CreateUser()
{
var clientId = "<CLIENT_ID>";
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "<TENANT_ID>";
var clientSecret = "<CLIENT_SECRET>";
// using Azure.Identity;
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
// https://learn.microsoft.com/dotnet/api/azure.identity.clientsecretcredential
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret, options);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var user = new{
Email = "test#mail.dk",
DisplayName = "TestUser",
Username = "Someusername",
};
var invitation = new Invitation
{
InvitedUserEmailAddress = user.Email,
InvitedUser = new User
{
AccountEnabled = true,
DisplayName = "TestUser",
CreationType = "LocalAccount",
PasswordPolicies = "DisableStrongPassword",
PasswordProfile = new PasswordProfile
{
ForceChangePasswordNextSignIn = true,
Password = "Test123456",
}
},
InvitedUserType = "member",
SendInvitationMessage = true,
InviteRedirectUrl = "someurl.com"
};
await graphClient.Invitations
.Request()
.AddAsync(invitation);
Console.Write("completed");
}
}
Try to set Azure AD authority by .WithAuthority instead of WithTenantId.
There is a typo in your scopes. Required permission is User.ReadWrite.All not User.ReadWriteAll.
var scopes = new[] { "User.ReadWrite.All" };
...
var pca = PublicClientApplicationBuilder
.Create(clientId)
.WithAuthority($"https://login.microsoftonline.com/{tenantId}")
.WithDefaultRedirectUri()
.Build();
I'm using MS directline to integrate custom channel with a chatbot based on botframework.
I'm using below functions to generate token & start conversation, but none of them allows to set Context.Activity.From.Id, MembersAdded.FirstOrDefault().Id , nor Activity.Recipient.Id
GenerateTokenForNewConversationAsync()
StartConversationAsync()
I Know we can control the ID when we send the first user message through directline , but I want to control any of the above IDs before even sending a message from the user. I want to set a specific ID and be able to capture it on the BOT event OnTurnAsync and Activity of type ActivityTypes.ConversationUpdate .. what should I do ?
Regarding my comment, I decided I'd just provide how to do what you want with both packages. Again, if it isn't too much trouble to switch, I highly, highly recommend using Microsoft.Bot.Connector (newer and more frequently updated) over Microsoft.Bot.Connector.DirectLine (older, not updated in 2.5 years, and deprecated until/unless we open-source it Update: This isn't actually deprecated, yet. We're currently working on open-sourcing this, but it's a low-priority task).
Recommended: Microsoft.Bot.Connector
Create the conversation with To and From, all-in-one.
var userAccount = new ChannelAccount(toId,toName);
var botAccount = new ChannelAccount(fromId, fromName);
var connector = new ConnectorClient(new Uri(serviceUrl));
IMessageActivity message = Activity.CreateMessageActivity();
if (!string.IsNullOrEmpty(conversationId) && !string.IsNullOrEmpty(channelId))
{
message.ChannelId = channelId;
}
else
{
conversationId = (await connector.Conversations.CreateDirectConversationAsync( botAccount, userAccount)).Id;
}
message.From = botAccount;
message.Recipient = userAccount;
message.Conversation = new ConversationAccount(id: conversationId);
message.Text = "Hello, this is a notification";
message.Locale = "en-Us";
await connector.Conversations.SendToConversationAsync((Activity)message);
Credit
Not Recommended: Microsoft.Bot.Connector.DirectLine
This is kind of a hacky workaround, but basically, you create the conversation and then send a ConversationUpdate activity.
//server side, retrieve token from secret
string directLineSecret = ConfigurationManager.AppSettings["DirectLineSecret"];
HttpClient httpClient = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post,$"https://directline.botframework.com/v3/directline/tokens/generate");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", directLineSecret);
var fromUser = $"dl_{Guid.NewGuid()}";
request.Content = new StringContent(
JsonConvert.SerializeObject(
new { User = new { Id = fromUser } }),
Encoding.UTF8,
"application/json");
var response = await httpClient.SendAsync(request);
DirectLineToken dlToken = null;
if (response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync();
dlToken = JsonConvert.DeserializeObject<DirectLineToken>(body);
}
string token = dlToken.token;
//create DirectLineClient from token, client side
DirectLineClient client = new DirectLineClient(token);
var conversation = await client.Conversations.StartConversationAsync();
new System.Threading.Thread(async () => await ReadBotMessagesAsync(client, conversation.ConversationId)).Start();
//send conversationUpdate
var user = new ChannelAccount(fromUser);
await client.Conversations.PostActivityAsync(conversation.ConversationId,
new Activity
{
From = user,
Text = string.Empty,
Type = ActivityTypes.ConversationUpdate,
MembersAdded = new[] { user }
}
);
TimeSpan delayTime = TimeSpan.FromSeconds(dlToken.expires_in) - TimeSpan.FromMinutes(5);
Task.Factory.StartNew(async () =>
{
while (!_getTokenAsyncCancellation.IsCancellationRequested)
{
var t = await client.Tokens.RefreshTokenAsync().ConfigureAwait(false);
await Task.Delay(delayTime, _getTokenAsyncCancellation.Token).ConfigureAwait(false);
}
}).ConfigureAwait(false);
Credit
I'm trying to perform a simple operation of reading a user profile.
After I granted relevant permissions for this operation, I was able to acquire a token by writing the following code:
static void Main(string[] args)
{
var getToken = new GetTokenEntity()
{
Authority = "https://login.microsoftonline.com/common",
Resource = "https://graph.microsoft.com",
UserName = "myusername",
ClientId = "appclientidguid",
Password = "somepass"
};
var graphClient = new GraphServiceClient("https://graph.microsoft.com", new DelegateAuthenticationProvider(async(requestMessage) =>
{
var authResult = await GetToken(getToken);
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", authResult.AccessToken);
}));
var inbox = GetMessages(graphClient).GetAwaiter().GetResult();
}
public async static Task<AuthenticationResult> GetToken(GetTokenEntity getToken)
{
var authenticationContext = new AuthenticationContext(getToken.Authority);
var authenticationResult = await authenticationContext
.AcquireTokenAsync(getToken.Resource, getToken.ClientId,
new UserPasswordCredential(getToken.UserName, getToken.Password));
return authenticationResult;
}
public async static Task<User> GetMessages(GraphServiceClient graphClient)
{
var currentUser = await graphClient.Me.Request().GetAsync();
return currentUser;
}
Unfortunately, after receiving a token, this line: await graphClient.Me.Request().GetAsync(); fails with this exception:
Code: ResourceNotFound
Message: Invalid version: me
Inner error
I have checked my token in https://jwt.ms/ to and verified that "aud": "https://graph.microsoft.com".
As per the docs https://github.com/microsoftgraph/msgraph-sdk-dotnet/blob/dev/docs/overview.md (and the intellisense hint in visual studio) your base URL should be
https://graph.microsoft.com/currentServiceVersion
So this line
var graphClient = new GraphServiceClient("https://graph.microsoft.com", new DelegateAuthenticationProvider(async (requestMessage) =
should be either
var graphClient = new GraphServiceClient("https://graph.microsoft.com/v1.0", new DelegateAuthenticationProvider(async (requestMessage) =
or /beta if you want to use that
Problem is not with your token but you URL, it is missing a version number.
Right Query:
Your Query:
Hi all i'm trying to save some data from a login controller to the users data store.
[HttpGet, Route("api/{channelId}/{userId}/authorize")]
public async System.Threading.Tasks.Task<HttpResponseMessage> Authorize(string channelId, string userId, string code)
{
string protocalAndDomain = HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority);
AuthenticationContext ac = new AuthenticationContext(Constants.AD_AUTH_CONTEXT);
ClientCredential cc = new ClientCredential(Constants.AD_CLIENT_ID, Constants.AD_CLIENT_SECRET);
AuthenticationResult ar = await ac.AcquireTokenByAuthorizationCodeAsync(code, new Uri(protocalAndDomain + "/api/" + channelId + "/" + userId + "/authorize"), cc);
MicrosoftAppCredentials.TrustServiceUrl(protocalAndDomain, DateTime.Now.AddHours(1));
if (!String.IsNullOrEmpty(ar.AccessToken))
{
// Store access token & User Id to bot state
//var botCred = new MicrosoftAppCredentials(Constants.MS_APP_ID, Constants.MS_APP_PASSWORD);
//https://state.botframework.com
using (var sc = new StateClient(new Uri("http://localhost:3979/")))
if (sc != null)
{
var botData = new BotData(data: null, eTag: "*");
botData.SetProperty("accessToken", ar.AccessToken);
botData.SetProperty("userEmail", ar.UserInfo.DisplayableId);
//i get a 401 response here
await sc.BotState.SetUserDataAsync(channelId, userId, botData);
}
var response = Request.CreateResponse(HttpStatusCode.Moved);
response.Headers.Location = new Uri("/loggedin.html", UriKind.Relative);
return response;
}
else
return Request.CreateResponse(HttpStatusCode.Unauthorized);
}
I've seen examples in where you can use the AppId an appPassword to access the bot state, but to my understanding those aren't available until your bot is published/regested in the azuer application portal which i currently can't do.
or that you can access it via the activity which again i don't have access to.
this is actually just a temporary solution my plan is to eventually save the user data to Azure table storage, however i would like a temporary solution in the mean time; I'm considering serializing and deserializing a dictionary to local text file, but that seems like overkill for now and it seems silly that i can't locally save to the user data without having my app registered in azure.
cheers any help is much appreciated.
With this line:
var sc = new StateClient(new Uri("http://localhost:3979/"))
you are instructing the BotBuilder to use a State Service at http://localhost:3979/ But there is no State Service at that endpoint.
If you want to have a temporary solution, until you add Azure Table Storage, you can use the InMemoryDataStore:
protected void Application_Start()
{
Conversation.UpdateContainer(
builder =>
{
builder.RegisterModule(new AzureModule(Assembly.GetExecutingAssembly()));
var store = new InMemoryDataStore(); // volatile in-memory store
builder.Register(c => store)
.Keyed<IBotDataStore<BotData>>(AzureModule.Key_DataStore)
.AsSelf()
.SingleInstance();
});
GlobalConfiguration.Configure(WebApiConfig.Register);
}
Note: this requires the Azure Extensions nuget package https://www.nuget.org/packages/Microsoft.Bot.Builder.Azure/
Once the InMemoryDataStore is registered, you can access it using something like:
var message = new Activity()
{
ChannelId = ChannelIds.Directline,
From = new ChannelAccount(userId, userName),
Recipient = new ChannelAccount(botId, botName),
Conversation = new ConversationAccount(id: conversationId),
ServiceUrl = serviceUrl
}.AsMessageActivity();
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message))
{
var botDataStore = scope.Resolve<IBotDataStore<BotData>>();
var key = new AddressKey()
{
BotId = message.Recipient.Id,
ChannelId = message.ChannelId,
UserId = message.From.Id,
ConversationId = message.Conversation.Id,
ServiceUrl = message.ServiceUrl
};
var userData = await botDataStore.LoadAsync(key, BotStoreType.BotUserData, CancellationToken.None);
userData.SetProperty("key 1", "value1");
userData.SetProperty("key 2", "value2");
await botDataStore.SaveAsync(key, BotStoreType.BotUserData, userData, CancellationToken.None);
await botDataStore.FlushAsync(key, CancellationToken.None);
}
I get a 403 Forbidden response from Azure AD when trying to create an application using the Graph API:
private static void CreateApplicationViaPost(string tenantId, string clientId, string clientSecret)
{
var authContext = new AuthenticationContext(
string.Format("https://login.windows.net/{0}",
tenantId));
ClientCredential clientCred = new ClientCredential(clientId, clientSecret);
AuthenticationResult result = authContext.AcquireToken(
"https://graph.windows.net",
clientCred);
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
const string json = #"{ displayName: ""My test app"", logoutUrl: ""http://logout.net"", identifierUris: [ ""http://identifier1.com"" ], replyUrls: [ ""http://replyUrl.net"" ] }";
HttpResponseMessage response = client.PostAsync(
string.Format("https://graph.windows.net/{0}/applications?api-version=1.6", tenantId),
new StringContent(json, Encoding.UTF8, "application/json")).Result;
Console.WriteLine(response.ToString());
}
The client registered in Azure AD has all the permissions:
What am I missing?
EDIT:
I registered a native client in Azure AD and gave it permissions to write to Windows Azure Active Directory. This code create an application in Azure AD:
private static void CreateApplicationViaPost(string tenantId, string clientId, string redirectUri)
{
var authContext = new AuthenticationContext(
string.Format("https://login.windows.net/{0}",
tenantId));
AuthenticationResult result = authContext.AcquireToken("https://graph.windows.net", clientId, new Uri(redirectUri), PromptBehavior.Auto);
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
const string json = #"{ displayName: ""My test app1"", homepage: ""http://homepage.com"", logoutUrl: ""http://logout1.net"", identifierUris: [ ""http://identifier11.com"" ], replyUrls: [ ""http://replyUrl1.net"" ] }";
HttpResponseMessage response = client.PostAsync(
string.Format("https://graph.windows.net/{0}/applications?api-version=1.6", tenantId),
new StringContent(json, Encoding.UTF8, "application/json")).Result;
Console.WriteLine(response.ToString());
}
Modifying the directory requires consent from an admin user. So you'll need to acquire an access token from an user, e.g. through OAuth, instead of a token for the client.
There are quite a few of samples at GitHub that show the authorisation flow, e.g. https://github.com/AzureADSamples/WebApp-GraphAPI-DotNet.
Adding to #MrBrink's answer - you need to make sure the person adding the permissions in the Azure Active Directory UI is actually an administrator. If you have access to Azure Active Directory and are not an administrator it WILL still let you assign permissions - however they will only apply at a user scope.
An alternative would be to use the ActiveDirectoryClient from the Microsoft.Azure.ActiveDirectory.GraphClient NuGet package.
private static async Task CreateApplication(string tenantId, string clientId,
string redirectUri)
{
var graphUri = new Uri("https://graph.windows.net");
var serviceRoot = new Uri(graphUri, tenantId);
var activeDirectoryClient = new ActiveDirectoryClient(serviceRoot,
async () => AcquireTokenAsyncForUser("https://login.microsoftonline.com/" + tenantId,
clientId, redirectUri));
var app = new Application
{
Homepage = "https://localhost",
DisplayName = "My Application",
LogoutUrl = "https://localhost",
IdentifierUris = new List<string> { "https://tenant.onmicrosoft.com/MyApp" },
ReplyUrls = new List<string> { "https://localhost" }
};
await activeDirectoryClient.Applications.AddApplicationAsync(app);
Console.WriteLine(app.ObjectId);
}
private static string AcquireTokenAsyncForUser(string authority, string clientId,
string redirectUri)
{
var authContext = new AuthenticationContext(authority, false);
var result = authContext.AcquireToken("https://graph.windows.net",
clientId, new Uri(redirectUri), PromptBehavior.Auto);
return result.AccessToken;
}