cljung / b2c-approles Goto Github PK
View Code? Open in Web Editor NEWAzure AD B2C AppRoles sample
Azure AD B2C AppRoles sample
I using this guide to setup Azure Ad B2c. After that I this guide to custom policy to use RBAC.
BUT I got an issue is: I use postman to get token
My allication in Azure AD B2c:
This is my authentication setting in MyWebApp
When I Signup and Get Token, The response doesn't contain accesstoken:
My Custom policy:
https://github.com/wangyiwu/AzureAdPolicy
Error are:
Hi Christer,
Thanks for writing excellent article for delegated admin and RBAC for Azure AD B2C. It has helped us a lot to understand how platform works. We are currently trying to setup Delegated admin poc but the code you have shared here in the github is missing AccountController.cs file. We are not able to redirected to our Azure AD B2C login page once we click on SignIn link of the application running locally from VisualStudio2019.
Can you please update your code in the repository so that we can run it locally? Please note that I am new to .net platfrom.
Looking forward for your reply.
Best Regards from Oslo,
Sachin Dave
I have a Azure Function app, I create A Function:
#r "Newtonsoft.Json"
using System.Net;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public static async Task<HttpResponseMessage> Run(HttpRequest req, ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string objectId = req.Query["objectId"]; // User
string clientId = req.Query["clientId"]; // App
string tenantId = req.Query["tenantId"]; // Tenant
string scope = req.Query["scope"]; // "roles", "roles groups" or "groups"
log.LogInformation("objectId: " + objectId);
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
objectId = objectId ?? data?.objectId;
clientId = clientId ?? data?.clientId;
tenantId = tenantId ?? data?.tenantId;
scope = scope ?? data?.scope;
// default just roles
if ( string.IsNullOrWhiteSpace(scope) ) {
scope = "roles";
} else {
scope = scope.ToLowerInvariant();
}
log.LogInformation($"Params: objectId={objectId}, clientId={clientId}, tenantId={tenantId}, scope={scope}" );
//////////////////////////////////////////////////////////////////////////////////////////////////////
// If you use Cert based auth, you will receive a client cert
//////////////////////////////////////////////////////////////////////////////////////////////////////
var cert = req.HttpContext.Connection.ClientCertificate;
log.LogInformation($"Incoming cert: {cert}");
if(cert != null ) {
var b2cCertSubject = System.Environment.GetEnvironmentVariable( $"B2C_{tenantId}_CertSubject"); //
var b2cCertThumbprint = System.Environment.GetEnvironmentVariable($"B2C_{tenantId}_CertThumbprint");
if ( !( cert.Subject.Equals(b2cCertSubject) && cert.Thumbprint.Equals(b2cCertThumbprint) ) ) {
var respContent = new { version = "1.0.0", status = (int)HttpStatusCode.BadRequest,
userMessage = "Technical error - cert..."};
var json = JsonConvert.SerializeObject(respContent);
log.LogInformation(json);
return new HttpResponseMessage(HttpStatusCode.Conflict) {
Content = new StringContent(json, System.Text.Encoding.UTF8, "application/json")
};
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
// get the B2C client credentials for this tenant
//////////////////////////////////////////////////////////////////////////////////////////////////////
var b2cClientId = System.Environment.GetEnvironmentVariable($"B2C_{tenantId}_ClientId"); //
var b2cClientSecret = System.Environment.GetEnvironmentVariable($"B2C_{tenantId}_ClientSecret");
//////////////////////////////////////////////////////////////////////////////////////////////////////
// Authenticate via the Client Credentials flow
//////////////////////////////////////////////////////////////////////////////////////////////////////
string accessToken = GetCachedAccessToken( tenantId );
log.LogInformation($"accessToken: {accessToken}");
if ( null == accessToken )
{
HttpClient client = new HttpClient();
var dict= new Dictionary<string, string>();
dict.Add("grant_type", "client_credentials");
dict.Add("client_id", b2cClientId);
dict.Add("client_secret", b2cClientSecret);
dict.Add("resource", "https://graph.microsoft.com");
dict.Add("scope", "User.Read.All AppRoleAssignment.Read.All");
var urlTokenEndpoint = $"https://login.microsoftonline.com/{tenantId}/oauth2/token?api-version=1.0";
log.LogInformation(urlTokenEndpoint);
HttpResponseMessage resp = client.PostAsync( urlTokenEndpoint, new FormUrlEncodedContent(dict)).Result;
var contents = await resp.Content.ReadAsStringAsync();
client.Dispose();
log.LogInformation("HttpStatusCode=" + resp.StatusCode.ToString() + " - " + contents );
// If the client creds failed, return error
if ( resp.StatusCode != HttpStatusCode.OK ) {
var respContent = new { version = "1.0.0", status = (int)HttpStatusCode.BadRequest, userMessage = "Technical error..."};
var json = JsonConvert.SerializeObject(respContent);
log.LogInformation(json);
return new HttpResponseMessage(HttpStatusCode.Conflict) {
Content = new StringContent(json, System.Text.Encoding.UTF8, "application/json")
};
}
accessToken = JObject.Parse(contents)["access_token"].ToString();
CacheAccessToken( tenantId, accessToken );
}
log.LogInformation(accessToken);
//////////////////////////////////////////////////////////////////////////////////////////////////////
// GraphAPI query for user's group membership
//////////////////////////////////////////////////////////////////////////////////////////////////////
var groupsList = new List<string>();
if ( scope.Contains("groups") ) {
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken );
var url = $"https://graph.microsoft.com/v1.0/users/{objectId}/memberOf?$select=id,displayName";
log.LogInformation(url);
var res = await httpClient.GetAsync(url);
log.LogInformation("HttpStatusCode=" + res.StatusCode.ToString());
if(res.IsSuccessStatusCode) {
var respData = await res.Content.ReadAsStringAsync();
var groupArray = (JArray)JObject.Parse(respData)["value"];
foreach (JObject g in groupArray) {
var name = g["displayName"].Value<string>();
groupsList.Add(name);
}
}
httpClient.Dispose();
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
// GraphAPI query for user AppRoleAssignments
//////////////////////////////////////////////////////////////////////////////////////////////////////
JObject userData = null;
bool hasAssignments = false;
var roleNames = new List<string>();
if ( scope.Contains("roles") ) {
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken );
var url = $"https://graph.microsoft.com/beta/users/{objectId}/appRoleAssignments?$select=appRoleId,resourceId,resourceDisplayName";
log.LogInformation(url);
var res = await httpClient.GetAsync(url);
log.LogInformation("HttpStatusCode=" + res.StatusCode.ToString());
if(res.IsSuccessStatusCode) {
var respData = await res.Content.ReadAsStringAsync();
log.LogInformation(respData);
userData = JObject.Parse(respData);
foreach( var item in userData["value"] ) {
hasAssignments = true;
break;
}
}
httpClient.Dispose();
}
if ( hasAssignments ) {
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
var url = $"https://graph.microsoft.com/beta/servicePrincipals?$filter=appId eq '{clientId}'&$select=id,appRoles";
log.LogInformation(url);
var res = httpClient.GetAsync(url).Result;
JArray appRolesArray = null;
string spId = "";
if (res.IsSuccessStatusCode) {
var respData = res.Content.ReadAsStringAsync().Result;
var spoData = JObject.Parse(respData);
foreach (var item in spoData["value"]) {
spId = item["id"].ToString();
appRolesArray = (JArray)item["appRoles"];
foreach (var itemUser in userData["value"]) {
if (spId == itemUser["resourceId"].ToString()) {
var appRoleId = itemUser["appRoleId"].ToString();
foreach (var role in appRolesArray) {
if (appRoleId == role["id"].ToString()) {
roleNames.Add(role["value"].ToString());
}
}
}
}
}
}
httpClient.Dispose();
}
var jsonToReturn = JsonConvert.SerializeObject( new { roles = roleNames, groups = groupsList } );
log.LogInformation(jsonToReturn);
return new HttpResponseMessage(HttpStatusCode.OK) {
Content = new StringContent(jsonToReturn, System.Text.Encoding.UTF8, "application/json")
};
}
public static string GetCachedAccessToken( string tenantId, int secondsRemaining = 60 ) // access_token needs to be valid for N seconds more
{
string accessToken = Environment.GetEnvironmentVariable($"B2C_{tenantId}_AppRoles_AccessToken");
if (accessToken != null)
{
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
string b64 = accessToken.Split(".")[1];
while ((b64.Length % 4) != 0)
b64 += "=";
JObject jwtClaims = JObject.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(b64)));
DateTime expiryTime = epoch.AddSeconds(int.Parse(jwtClaims["exp"].ToString()));
if (DateTime.UtcNow >= expiryTime.AddSeconds(-secondsRemaining))
accessToken = null; // invalidate
}
return accessToken;
}
public static void CacheAccessToken( string tenantId, string accesToken )
{
Environment.SetEnvironmentVariable($"B2C_{tenantId}_AppRoles_AccessToken", accesToken);
}
This is function.json
"bindings": [
{
"authLevel": "function",
"name": "req",
"type": "httpTrigger",
"direction": "in",
"methods": [
"get",
"post"
]
},
{
"name": "$return",
"type": "http",
"direction": "out"
}
]
}
When I Run, I get Logs:
Connected!
2022-06-15T08:40:52.461 [Information] Executing 'Functions.GetAppRoleAssignmentsMSGraph' (Reason='This function was programmatically called via the host APIs.', Id=b0dec087-1c3a-4140-94b8-abf442845793)
2022-06-15T08:40:52.461 [Information] C# HTTP trigger function processed a request.
2022-06-15T08:40:52.461 [Information] objectId: 76ae8450-37ee-4cec-b27e-edffe607fd08
2022-06-15T08:40:52.461 [Information] Params: objectId=76ae8450-37ee-4cec-b27e-edffe607fd08, clientId=95155f6c-a52b-4181-ad39-45e89701de6d, tenantId=7d73c792-46b8-402b-b77f-38ce079a3dd7", scope=roles
2022-06-15T08:40:52.461 [Information] Incoming cert:
2022-06-15T08:40:52.462 [Information] accessToken:
2022-06-15T08:40:52.462 [Information] https://login.microsoftonline.com/7d73c792-46b8-402b-b77f-38ce079a3dd7"/oauth2/token?api-version=1.0
2022-06-15T08:40:52.681 [Information] HttpStatusCode=Unauthorized - {"error":"invalid_client","error_description":"AADSTS7000216: 'client_assertion', 'client_secret' or 'request' is required for the 'client_credentials' grant type.\r\nTrace ID: 396474fe-7af3-4b2f-ad01-44283acb2b00\r\nCorrelation ID: 03c73d62-7776-4f2c-b2b3-c2b0c7457098\r\nTimestamp: 2022-06-15 08:40:52Z","error_codes":[7000216],"timestamp":"2022-06-15 08:40:52Z","trace_id":"396474fe-7af3-4b2f-ad01-44283acb2b00","correlation_id":"03c73d62-7776-4f2c-b2b3-c2b0c7457098","error_uri":"https://login.microsoftonline.com/error?code=7000216"}
2022-06-15T08:40:52.681 [Information] {"version":"1.0.0","status":400,"userMessage":"Technical error..."}
2022-06-15T08:40:52.681 [Information] Executed 'Functions.GetAppRoleAssignmentsMSGraph' (Succeeded, Id=b0dec087-1c3a-4140-94b8-abf442845793, Duration=220ms)
2022-06-15T08:42:13 No new trace in the past 1 min(s).
This is my Query
![anh](https://i.imgur.com/0d58L4d.png)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.