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
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 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 used this HTTP-Get request to get a Bearer token for the translation:
https://api.cognitive.microsoft.com/sts/v1.0/issueToken?Subscription-Key=1fo8xxx
Using the returned Bearer I wanted to translate a short text using this API endpoint:
https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to=de
In the header I put this:
Content-Type: application/json; charset=UTF-8.
And in the body I put this:
[
{"Text":"I would really like to drive your car around the block a few times."}
]
I am using Postman, so in the authorization tab I selected Bearer and inserted in the field next to it this:
Bearer <result from the first API call>
If I send the reqeuest I get this result:
{"error":{"code":401000,"message":"The request is not authorized because credentials are missing or invalid."}}
In case someone ever stumbles upon this, after hours of trial and error I found out you need to pass the Ocp-Apim-Subscription-Region param in the header.
Here is an example in python that I was able to run successfully.
import json
import requests
def translate(text, source_language, dest_language):
if not <Secret Key>:
return 'Error: the translation service is not configured.'
headers = {'Ocp-Apim-Subscription-Key': <Secret Key>,
'Ocp-Apim-Subscription-Region': <region>,
'Content-type': 'application/json'}
url = 'https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&from={}&to={}' \
.format(source_language, dest_language)
body = [{'text': text}]
request = requests.post(url, headers=headers, json=body)
if request.status_code != 200:
return 'Error: the translation service failed.'
return json.loads(request.content.decode('utf-8-sig'))
The list of regions and other examples can be found here:
https://learn.microsoft.com/en-us/azure/cognitive-services/translator/reference/v3-0-reference
Don't be fooled by the curl example that is not using the region..
Your request needs the "OCP-Apim-Subscription-Key" header. Take a look on the official example:
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
// Install Newtonsoft.Json with NuGet
using Newtonsoft.Json;
/// <summary>
/// The C# classes that represents the JSON returned by the Translator Text API.
/// </summary>
public class TranslationResult
{
public DetectedLanguage DetectedLanguage { get; set; }
public TextResult SourceText { get; set; }
public Translation[] Translations { get; set; }
}
public class DetectedLanguage
{
public string Language { get; set; }
public float Score { get; set; }
}
public class TextResult
{
public string Text { get; set; }
public string Script { get; set; }
}
public class Translation
{
public string Text { get; set; }
public TextResult Transliteration { get; set; }
public string To { get; set; }
public Alignment Alignment { get; set; }
public SentenceLength SentLen { get; set; }
}
public class Alignment
{
public string Proj { get; set; }
}
public class SentenceLength
{
public int[] SrcSentLen { get; set; }
public int[] TransSentLen { get; set; }
}
private const string key_var = "TRANSLATOR_TEXT_SUBSCRIPTION_KEY";
private static readonly string subscriptionKey = Environment.GetEnvironmentVariable(key_var);
private const string endpoint_var = "TRANSLATOR_TEXT_ENDPOINT";
private static readonly string endpoint = Environment.GetEnvironmentVariable(endpoint_var);
static Program()
{
if (null == subscriptionKey)
{
throw new Exception("Please set/export the environment variable: " + key_var);
}
if (null == endpoint)
{
throw new Exception("Please set/export the environment variable: " + endpoint_var);
}
using (var client = new HttpClient())
using (var request = new HttpRequestMessage())
{
// Build the request.
// Set the method to Post.
request.Method = HttpMethod.Post;
// Construct the URI and add headers.
request.RequestUri = new Uri(endpoint + route);
request.Content = new StringContent(requestBody, Encoding.UTF8, "application/json");
request.Headers.Add("Ocp-Apim-Subscription-Key", subscriptionKey);
// Send the request and get response.
HttpResponseMessage response = await client.SendAsync(request).ConfigureAwait(false);
// Read response as a string.
string result = await response.Content.ReadAsStringAsync();
// Deserialize the response using the classes created earlier.
TranslationResult[] deserializedOutput = JsonConvert.DeserializeObject<TranslationResult[]>(result);
// Iterate over the deserialized results.
foreach (TranslationResult o in deserializedOutput)
{
// Print the detected input language and confidence score.
Console.WriteLine("Detected input language: {0}\nConfidence score: {1}\n", o.DetectedLanguage.Language, o.DetectedLanguage.Score);
// Iterate over the results and print each translation.
foreach (Translation t in o.Translations)
{
Console.WriteLine("Translated to {0}: {1}", t.To, t.Text);
}
}
}
Console.Read();
}
https://learn.microsoft.com/en-us/azure/cognitive-services/translator/quickstart-translate?pivots=programming-language-csharp
I am trying to check the replication role of a SQL Azure database using the Azure SDK for .NET.
I used the SqlManagementClient to fetch databases from our subscription but there are no properties indicating the replication role.
I used the following code to fetch databases.
var client = GetSqlManagementClient();
var database = client.Databases
.List("<serverName>")
.First(x => x.Name == "<databaseName>");
Is there another way to get this information that I am missing?
If you means to read secondly location, I think Azure ARM management could help us to do this. I have tried it on my local and get the result as followings:
[Update]
Here is my test code:
public async Task<string> GetToken()
{
AuthenticationResult result = null;
string test;
AuthenticationContext authContext = new AuthenticationContext(adInfo.AuthUrl + adInfo.Telnant);
ClientCredential cc = new ClientCredential(adInfo.ClientId, adInfo.ClientSecret);
try
{
result = await authContext.AcquireTokenAsync(adInfo.Resource,cc);
test = result.AccessToken;
return test;
}
catch (AdalException ex)
{
return ex.Message;
}
}
public async Task GetSQLInfo()
{
string token = await GetToken();
var sqlclient = new SqlManagementClient(new TokenCloudCredentials(adApplication.Subscription, token));
var data = await sqlclient.Databases.GetAsync("jatestgroup", "jaserver", "jasql");
}
Here is my class about adInfo and adApplication:
public class AdInfo
{
[JsonProperty(PropertyName = "clientid")]
public string ClientId { get; set; }
[JsonProperty(PropertyName = "clientsecret")]
public string ClientSecret { get; set; }
[JsonProperty(PropertyName = "returnurl")]
public string ReturnUrl { get; set; }
[JsonProperty(PropertyName = "telnantid")]
public string Telnant { get; set; }
[JsonProperty(PropertyName = "authurl")]
public string AuthUrl { get; set; }
[JsonProperty(PropertyName = "resource")]
public string Resource { get; set; }
}
public class AdApplication
{
[JsonProperty(PropertyName = "ARMTemplate")]
public AdInfo Application { get; set; }
[JsonProperty(PropertyName = "subscription")]
public string Subscription { get; set; }
}
My Json Settings:
{
"ARMTemplate": {
"clientid": "****",
"clientsecret": "****",
"returnurl": "http://localhost:20190",
"telnantid": "****",
"authurl": "https://login.microsoftonline.com/",
"resource": "https://management.core.windows.net/"
},
"subscription": "***"
}
Since this issue is more related with Auth failed. I would suggest you create a new thread and give more info for us if my code does not give you help.
I am using IPWorks nsoftware for creating service. In it, to call a service I am using
Rest rest = new Rest();
rest.Accept = "application/json";
rest.ContentType = "application/json";
rest.User = "UserName";
rest.Password = "Password";
rest.Get(#"http://Foo.com/roles.json");
string result = rest.TransferredData;
var listRoles = JsonSerializer.DeserializeFromString<List<role>>(result);
I am getting the Json response as a string
[{"role":{"name":"Administrator","created_at":"2012-02-11T09:53:54-02:00","updated_at":"2012-04-29T23:43:47-04:00","id":1"}},{"role":{"name":"NormalUser","created_at":"2013-02-11T08:53:54-02:00","updated_at":"2013-04-29T23:43:47-03:00","id":2"}}]
Here the json string contains my domain object “role” which gets appended to my response (i.e the body style of the message is wrapped) .
I am using ServiceStack.Text’s Deserializer to convert the response string to my object. But since it’s wrapped, the deserilization is incorrect.
Is there anything that I am missing here ? Is there any “BodyStyle” attribute which could be added to the Rest request?
The GitHubRestTests shows some of the different ways you can deserialize a 3rd party json API with ServiceStack's JSON Serializer.
If you want to deserialize it into typed POCOs then judging by your JSON payload the typed POCOs should look something like:
public class RolePermissionWrapper
{
public Role Role { get; set; }
public Permission Permission { get; set; }
}
public class Role
{
public long Id { get; set; }
public string Name { get; set; }
public DateTime? Created_At { get; set; }
public DateTime? Updated_At { get; set; }
}
var listRoles = JsonSerializer.DeserializeFromString<List<RolePermissionWrapper>>(result);