Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
What's the recommended way to create Azure function to create a AAD Service principal.
Should we be doing Azure function using Powershell maybe?
As per your comment To Create User From Azure function using client_credentials grant flow Here I am giving you exact sample for azure function. Just plug and play :))
Example Contains:
How Would you get token using client_credentials flow
Create User on Azure Active Directory tenant Azure Function
Access Token Class:
public class AccessTokenClass
{
public string token_type { get; set; }
public string expires_in { get; set; }
public string resource { get; set; }
public string scope { get; set; }
public string access_token { get; set; }
}
Azure Active Directory Create User Class:
public class AzureFunctionCreateUserClass
{
public bool accountEnabled { get; set; }
public string displayName { get; set; }
public string mailNickname { get; set; }
public string userPrincipalName { get; set; }
public PasswordProfile passwordProfile { get; set; }
}
Azure Active Directory User Password Profile Class:
public class PasswordProfile
{
public bool forceChangePasswordNextSignIn { get; set; }
public string password { get; set; }
}
Reference To Add:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Net.Http;
using System.Collections.Generic;
using System.Net.Http.Headers;
Azure Function Body:
[FunctionName("FunctionCreateUserUsingRestAPI")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
try
{
log.LogInformation("C# HTTP trigger function processed a request.");
//Read Request Body
var content = await new StreamReader(req.Body).ReadToEndAsync();
//Extract Request Body and Parse To Class
AzureFunctionCreateUserClass objFuncRequestClass = JsonConvert.DeserializeObject<AzureFunctionCreateUserClass>(content);
// Variable For Validation message return
dynamic validationMessage;
// Validate param I am checking here. For Testing I am not taking from here But you can
if (string.IsNullOrEmpty(objFuncRequestClass.displayName))
{
validationMessage = new OkObjectResult("displayName is required!");
return (IActionResult)validationMessage;
}
if (string.IsNullOrEmpty(objFuncRequestClass.mailNickname))
{
validationMessage = new OkObjectResult("mailNicknameis required!");
return (IActionResult)validationMessage;
}
if (string.IsNullOrEmpty(objFuncRequestClass.userPrincipalName))
{
validationMessage = new OkObjectResult("userPrincipalName is required Format: UserName#YourTenant.onmicrosoft.com!");
return (IActionResult)validationMessage;
}
//Token Request Endpoint
string tokenUrl = $"https://login.microsoftonline.com/YourTenant.onmicrosoft.com/oauth2/token";
var tokenRequest = new HttpRequestMessage(HttpMethod.Post, tokenUrl);
tokenRequest.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = "b603c7be-a866-Your_client_id-e6921e61f925",
["client_secret"] = "Vxf1SluKbgu4PF0N-client_Secret-SeZ8wL/Yp8ns4sc=",
["resource"] = "https://graph.microsoft.com"
});
dynamic json;
AccessTokenClass results = new AccessTokenClass();
HttpClient client = new HttpClient();
//Request For Token
var tokenResponse = await client.SendAsync(tokenRequest);
json = await tokenResponse.Content.ReadAsStringAsync();
//Extract Token Into class
results = JsonConvert.DeserializeObject<AccessTokenClass>(json);
var accessToken = results.access_token;
//Azure Ad Password profile object
PasswordProfile objPass = new PasswordProfile();
objPass.forceChangePasswordNextSignIn = true;
objPass.password = "yourNewUserPass";
//Azure AD user Object
AzureFunctionCreateUserClass objCreateUser = new AzureFunctionCreateUserClass();
objCreateUser.accountEnabled = true;
objCreateUser.displayName = "KironFromFucntion";
objCreateUser.mailNickname = "KironMailFromFunction";
objCreateUser.userPrincipalName = "UserName#YourTenant.onmicrosoft.com";
objCreateUser.passwordProfile = objPass;
//Convert class object to JSON
var jsonObj = JsonConvert.SerializeObject(objCreateUser);
var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json");
using (HttpClient clientNew = new HttpClient())
{
var postJsonContent = new StringContent(jsonObj, Encoding.UTF8, "application/json");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Post Rquest To Create User Rest Endpoint URL: https://graph.microsoft.com/v1.0/users
var rsponseFromApi= await client.PostAsync("https://graph.microsoft.com/v1.0/users", postJsonContent);
//Check Reqeust Is Successfull
if (rsponseFromApi.IsSuccessStatusCode)
{
var result_string = await responseFromApi.Content.ReadAsStringAsync();
dynamic responseResults = JsonConvert.DeserializeObject<dynamic>(result_string);
return new OkObjectResult(responseResults);
}
else
{
var result_string = await rsponseFromApi.Content.ReadAsStringAsync();
return new OkObjectResult(result_string);
}
}
}
catch (Exception ex)
{
return new OkObjectResult(ex.Message);
}
}
Request Format:
{
"accountEnabled": true,
"displayName": "displayName-value",
"mailNickname": "mailNickname-value",
"userPrincipalName": "upn-value#tenant-value.onmicrosoft.com",
"passwordProfile" : {
"forceChangePasswordNextSignIn": true,
"password": "password-value"
}
}
Check Newly Created User On Azure Portal:
Just to sure check your newly created user on Azure Portal All Users. See the screen shot below:
Point To Remember:
For Azure Active Directory Create users access make sure you have following permission:
User.ReadWrite.All
Permission Type: Application
You can check here. See the screen shot for better understanding: make sure you have clicked Grant admin consent for yourTenant after adding permission.
Note: This is how you can Create User on Azure Active Directory using Azure Function with Client_Credentials token flow token to a specific API endpoint efficiently.
Related
I am following the documentation here to return the blocking response https://learn.microsoft.com/en-us/azure/active-directory-b2c/add-api-connector?pivots=b2c-user-flow#example-of-a-blocking-response from api connector to azure ad b2c, however even after constructing the right response as shown in the documentation, I am still not able to show the blocking page for b2c user flow.
Note that this connector gets invoked at sign-in.
I have verified that the response from api which seems correct and looks like below
{
"version": "1.0.0",
"action": "ShowBlockPage",
"userMessage": "You must have a local account registered for Contoso."
}
With this, was hoping to see a blocking page as below (screenshot from docs) but b2c does not show it and goes straight to the connected application.
What did I miss? any pointers would be appreciated. TIA.
Here is my api connector's code
public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
// Get the request body
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
// If input data is null, show block page
if (data == null)
{
return (ActionResult)new OkObjectResult(new ResponseContent("ShowBlockPage", "There was a problem with your request."));
}
// Print out the request body
log.LogInformation("Request body: " + requestBody);
// check for issuer
if(data.identities != null)
{
string issuer = data.identities[0].issuer;
log.LogInformation("issuer detected: " + issuer);
if(issuer == "github.com")
{
log.LogInformation("Returning an error!");
//return (ActionResult)new BadRequestObjectResult(new ResponseContent("ValidationError", "Please provide a Display Name with at least five characters."));
return (ActionResult)new OkObjectResult(new ResponseContent("ShowBlockPage", "You must have a local account registered for Contoso."));
}
}
// Validation passed successfully, return `Allow` response.
return (ActionResult)new OkObjectResult(new ResponseContent()
{
jobTitle = "This value return by the API Connector"//,
// You can also return custom claims using extension properties.
//extension_CustomClaim = "my custom claim response"
});
}
and here is the ResponseContent class
public class ResponseContent
{
public const string ApiVersion = "1.0.0";
public ResponseContent()
{
this.version = ResponseContent.ApiVersion;
this.action = "Continue";
}
public ResponseContent(string action, string userMessage)
{
this.version = ResponseContent.ApiVersion;
this.action = action;
this.userMessage = userMessage;
if (action == "ValidationError")
{
this.status = "400";
}
}
public string version { get; }
public string action { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string userMessage { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string status { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string jobTitle { get; set; }
}
We ended up creating Microsoft support ticket for this and got response that this is by design. ShowBlockPage response works only with sign-up user flows and not with sign-in user flows.
I have an Azure B2C user flow. It is associated with an API Connector pointing to an Azure Function. The function returns a ResponseContent with extension claims:
public class ResponseContent
{
public const string ApiVersion = "1.0.0";
public ResponseContent()
{
this.version = ResponseContent.ApiVersion;
this.action = "Continue";
}
public ResponseContent(string action, string userMessage)
{
this.version = ResponseContent.ApiVersion;
this.action = action;
this.userMessage = userMessage;
}
public ResponseContent(string userTypes, string accountIdentifiers, string pricebookAuthorized, string portalAuthorized)
{
this.version = ResponseContent.ApiVersion;
this.action = "Continue";
this.extension_UserTypes = userTypes;
this.extension_AccountIdentifiers = accountIdentifiers;
this.extension_PricebookAuthorized = pricebookAuthorized;
this.extension_PortalAuthorized = portalAuthorized;
}
public string version { get; }
public string action { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string userMessage { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string extension_UserTypes { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string extension_AccountIdentifiers { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string extension_PricebookAuthorized { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string extension_PortalAuthorized { get; set; }
}
Here are the claims of the user flow:
When I run this Azure function using Postman, the following is returned:
{
"version": "1.0.0",
"action": "Continue",
"extension_UserTypes": "",
"extension_AccountIdentifiers": "",
"extension_PricebookAuthorized": "",
"extension_PortalAuthorized": ""
}
But when I try to run the user flow on Azure, I get
Microsoft.Identity.Client.MsalServiceException:
AADB2C90261: The claims exchange 'PreSendClaimsRestful' specified in
step '2' returned HTTP error response that could not be parsed.
What might be wrong, and how this can be diagnosed?
Please check if below points can help:
Each key value pair in the JSON is treated as string, string
collection or Boolean.
AADB2C may not deserialise the claim in the JSON you send. One may
need to deserialise the string at the API, or will have to return a
nested JSON object without the escape characters.
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
Reference: dotnet-external-identities-api-connector-azure-function-validate
ยท GitHub
To troubleshoot the unexpected response, try sending Azure AD B2C
logs to Application Insights.
References:
Azure B2C - REST API call Error
Add extra claims to an Azure B2C user flow using API connectors and
ASP.NET Core | (damienbod.com)
how-to-parse-json-in-net-core
I found the following document: CosmosDB grant permission to multiple resources?
The answer there states that after the resource token broker gets the Permissions feed of the user and sends it back to client:
FeedResponse<Permission> permFeed = await client.ReadPermissionFeedAsync(UriFactory.CreateUserUri("dbid", " userId"));
List<Permission> permList = permFeed.ToList();
The client app can then initialize an instance of the DocumentClient class and pass the list (provided that it will deserialize the Json to List<Permission>).
var jsonString = await response.Content.ReadAsStringAsync();
var permissions = JsonConvert.DeserializeObject<List<Permission>>(jsonString);
var client = new DocumentClient(new Uri(EndpointUri), permisions);
The problem that I have is that the Permission class has a Token property that has only a getter and no setter exists. The following source code is from Microsoft.Azure.Documents namespace.
namespace Microsoft.Azure.Documents
{
public class Permission : Resource
{
[JsonProperty(PropertyName = "resource")]
public string ResourceLink { get; set; }
[JsonProperty(PropertyName = "resourcePartitionKey")]
public PartitionKey ResourcePartitionKey { get; set; }
[JsonConverter(typeof (StringEnumConverter))]
[JsonProperty(PropertyName = "permissionMode")]
public PermissionMode PermissionMode { get; set; }
[JsonProperty(PropertyName = "_token")]
public string Token { get; } <------------------------------------- HERE
}
}
As such, trying to serialize the Token field, the value copied is null.
Anyone has any solution for that?
I am a newbie of Microsoft bot.
My company have their own communication application, and i wanna connect my bot with communication application, my client can use my bot on my campany's communication application. I read about it need to use Direct Line to do it. But i really don't know how to do it.
Does anybody help me ?or give me some suggestions? or any example of that.
Thank you so much.
Please refer documentation about direct line approach for Bot framework.
What you have to do is use https://directline.botframework.com/api/conversations as your endpoint and call those API as shown in the documentation.
Example :- I just tried with ASP.MVC application. I created a text box and button for submit message to bot.
First enable direct link in your bot application. Then remember that secret.
Following code sample shows you how to connect your chat app or your company app with bot you built using bot frame work.
First you need to authorize your access to direct link API.
client = new HttpClient();
client.BaseAddress = new Uri("https://directline.botframework.com/api/conversations/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("BotConnector", "[Your Secret Key Here]");
response = await client.GetAsync("/api/tokens/");
if (response.IsSuccessStatusCode)
If you are success with previous response you can start a new Conversation
Model -
public class Conversation
{
public string conversationId { get; set; }
public string token { get; set; }
public string eTag { get; set; }
}
Code inside controller -
var conversation = new Conversation();
response = await client.PostAsJsonAsync("/api/conversations/",conversation);
if (response.IsSuccessStatusCode)
If you success with this response you will get conversationId and a token to start messaging.
Then pass your message to bot via following code,
Conversation ConversationInfo = response.Content.ReadAsAsync(typeof(Conversation)).Result as Conversation;
string conversationUrl = ConversationInfo.conversationId+"/messages/";
Message msg = new Message() { text = message };
response = await client.PostAsJsonAsync(conversationUrl,msg);
if (response.IsSuccessStatusCode)
If you get a success response, that means you have already sent your message to the bot. Now you need to get the reply message from BOT
To get the message from bot,
response = await client.GetAsync(conversationUrl);
if (response.IsSuccessStatusCode){
MessageSet BotMessage = response.Content.ReadAsAsync(typeof(MessageSet)).Result as MessageSet;
ViewBag.Messages = BotMessage;
IsReplyReceived = true;
}
Here you get a Message set, That means the message you sent and the reply from the Bot. You can now display it in your chat window.
Message Model -
public class MessageSet
{
public Message[] messages { get; set; }
public string watermark { get; set; }
public string eTag { get; set; }
}
public class Message
{
public string id { get; set; }
public string conversationId { get; set; }
public DateTime created { get; set; }
public string from { get; set; }
public string text { get; set; }
public string channelData { get; set; }
public string[] images { get; set; }
public Attachment[] attachments { get; set; }
public string eTag { get; set; }
}
public class Attachment
{
public string url { get; set; }
public string contentType { get; set; }
}
Using those API calls you can easily connect any of your custom chat applications with bot framework. Below is the full code inside one method for you to get idea about how you can archive your goal.
private async Task<bool> PostMessage(string message)
{
bool IsReplyReceived = false;
client = new HttpClient();
client.BaseAddress = new Uri("https://directline.botframework.com/api/conversations/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("BotConnector", "[Your Secret Code Here]");
response = await client.GetAsync("/api/tokens/");
if (response.IsSuccessStatusCode)
{
var conversation = new Conversation();
response = await client.PostAsJsonAsync("/api/conversations/", conversation);
if (response.IsSuccessStatusCode)
{
Conversation ConversationInfo = response.Content.ReadAsAsync(typeof(Conversation)).Result as Conversation;
string conversationUrl = ConversationInfo.conversationId+"/messages/";
Message msg = new Message() { text = message };
response = await client.PostAsJsonAsync(conversationUrl,msg);
if (response.IsSuccessStatusCode)
{
response = await client.GetAsync(conversationUrl);
if (response.IsSuccessStatusCode)
{
MessageSet BotMessage = response.Content.ReadAsAsync(typeof(MessageSet)).Result as MessageSet;
ViewBag.Messages = BotMessage;
IsReplyReceived = true;
}
}
}
}
return IsReplyReceived;
}
Thanks Cheers with you bot.
I've been working with ServiceStack and it's Auth providers. Specifically "FacebookAuthProvider".
My issue here is that the service is called from an iOS app. This app already have a valid access token and i just want to pass this value to servicestack facebook authentication.
I've seen the tests on servicestack github page, but it still doesn't make sense to me.
Is it possible to pass this access token to servicestack, so the authentication skips the part where i ask for permission, since we already did the on the app?
Or am i approching this the wrong way?
Instead of using the builtin facebook auth provider i created my own CustomFacebookAuthProvider.
The reason is that the builtin version needs a browser to redirect the user to facebook for authentication and i didn't need that. I already had an access token.
So based on the official version FacebookAuthProvider.cs i created my own.
using System;
using System.Collections.Generic;
using System.Net;
using Elmah;
using Mondohunter.Backend.BusinessLogic.Interfaces;
using ServiceStack.Common.Extensions;
using ServiceStack.Common.Web;
using ServiceStack.Configuration;
using ServiceStack.ServiceInterface;
using ServiceStack.ServiceInterface.Auth;
using ServiceStack.Text;
using ServiceStack.WebHost.Endpoints;
namespace Mondohunter.Interfaces
{
public class CustomFacebookAuthProvider : OAuthProvider
{
public const string Name = "facebook";
public static string Realm = "https://graph.facebook.com/";
public static string PreAuthUrl = "https://www.facebook.com/dialog/oauth";
public string AppId { get; set; }
public string AppSecret { get; set; }
public string[] Permissions { get; set; }
public CustomFacebookAuthProvider(IResourceManager appSettings)
: base(appSettings, Realm, Name, "AppId", "AppSecret")
{
this.AppId = appSettings.GetString("oauth.facebook.AppId");
this.AppSecret = appSettings.GetString("oauth.facebook.AppSecret");
}
public override object Authenticate(IServiceBase authService, IAuthSession session, Auth request)
{
var tokens = Init(authService, ref session, request);
try
{
if (request.oauth_token.IsNullOrEmpty())
throw new Exception();
tokens.AccessToken = request.oauth_token;
session.IsAuthenticated = true;
var json = AuthHttpGateway.DownloadFacebookUserInfo(request.oauth_token);
var authInfo = JsonSerializer.DeserializeFromString<Dictionary<string, string>>(json);
//Here i need to update/set userauth id to the email
//UpdateUserAuthId(session, authInfo["email"]);
authService.SaveSession(session, SessionExpiry);
OnAuthenticated(authService, session, tokens, authInfo);
//return json/xml/... response;
}
catch (WebException ex)
{
//return json/xml/... response;
}
catch (Exception ex)
{
//return json/xml/... response;
}
}
protected override void LoadUserAuthInfo(AuthUserSession userSession, IOAuthTokens tokens, Dictionary<string, string> authInfo)
{
if (authInfo.ContainsKey("id"))
tokens.UserId = authInfo.GetValueOrDefault("id");
if (authInfo.ContainsKey("name"))
tokens.DisplayName = authInfo.GetValueOrDefault("name");
if (authInfo.ContainsKey("first_name"))
tokens.FirstName = authInfo.GetValueOrDefault("first_name");
if (authInfo.ContainsKey("last_name"))
tokens.LastName = authInfo.GetValueOrDefault("last_name");
if (authInfo.ContainsKey("email"))
tokens.Email = authInfo.GetValueOrDefault("email");
if (authInfo.ContainsKey("gender"))
tokens.Gender = authInfo.GetValueOrDefault("gender");
if (authInfo.ContainsKey("timezone"))
tokens.TimeZone = authInfo.GetValueOrDefault("timezone");
LoadUserOAuthProvider(userSession, tokens);
}
public override void LoadUserOAuthProvider(IAuthSession authSession, IOAuthTokens tokens)
{
var userSession = authSession as CustomUserSession;
if (userSession == null) return;
userSession.Email = tokens.Email ?? userSession.PrimaryEmail ?? userSession.Email;
}
}
}
I hope it makes sense.