OpenIdConnectAuthenticationOptions set client ID programmatically based on the users E-Mail/Domain - c#

I have an existing public website written in MVC 4 which uses forms authentication and has user accounts configured in a SQL DB. I have a brief to add a single sign-on functionality which I have got working in this way. A user account will be created in our SQL where the E-Mail will match that of the active directory sign-in. This currently works, I can visit my app and it will auto-log me in.
I have configured an app registration in azure active directory which has given me the client ID and App Key to use. This is all well and good but as currently configured this limits me to only allowing one client to sign up for our service using single-sign on. What I would like to do is to configure a table which will store the domain, client ID and app key together so that when a visitor accesses the site we are able to "detect" their domain or email and thus find the client id and app key to use for THEIR active directory. This is a public facing website and it MUST continue to support the standard forms authentication login AND allow organisations to access it using their existing login credentials for their AD.
I also will need to look at how to default to the standard web form login page if no valid domain/client ID is found to allow users to log in as normal.
I would LOVE some help right now. For reference I am using the Startup.Auth.cs which visual studio kindly generated for me to handle the handshake. I have included the pertinent code below with notes and changes.
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions { });
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId, <--------- HERE THE CLIENT ID IS SET FROM A VARIABLE HARD CODED ABOVE
Authority = authority,
TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
// instead of using the default validation (validating against a single issuer value, as we do in line of business apps),
// we inject our own multitenant validation logic
ValidateIssuer = false,
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
SecurityTokenValidated = (context) =>
{
return Task.FromResult(0);
},
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
var email = context.JwtSecurityToken.Claims.First(x => x.Type == "unique_name").Value;
###################################################################
HERE I HAVE GOT THE E-MAIL SO I AM ABLE TO FIND THE DOMAIN AND FIND THE
CLIENT ID I NEED, BUT I NEED TO FIND THIS BEFORE THE LINE
app.UseOpenIdConnectAuthentication WHERE WE USE THE CLIENT ID
###################################################################
string[] EMailAddress = email.Split('#');
CallResponse callResponse = BusinessObjectSSO.SSO_GetApplicationDetailsByDomain(EMailAddress[1]);
if (callResponse.ResponseCode == 0)
{
DomainApplication domainApplication = (DomainApplication)callResponse.ReturnData;
clientId = domainApplication.ClientID;
appKey = domainApplication.Secret;
}
ClientCredential credential = new ClientCredential(clientId, appKey);
string tenantID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(aadInstance + tenantID, new ADALTokenCache(signedInUserID));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCodeAsync(code, new Uri("http://localhost:51437/"), credential, graphResourceID).Result;
SessionClass.SSOEMail = result.UserInfo.DisplayableId;
CallResponse callResponseUser = BusinessObjectSSO.SSO_AssignUserIDFromEMailAddress(signedInUserID, result.UserInfo.DisplayableId);
SessionClass.SSOUserID = Convert.ToInt32(callResponseUser.ReturnData);
return Task.FromResult(0);
},
AuthenticationFailed = (context) =>
{
context.OwinContext.Response.Redirect("Account/Error");
context.HandleResponse(); // Suppress the exception
return Task.FromResult(0);
}
}
});

Related

Adding Authorize(Roles = "foo") causes infinite redirect after Azure authentication

I have an MVC project that uses Azure AD as a connected service for single sign-on authentication of the user. That works fine, and any controller with the [Authorize] attribute works as expected.
I have two app roles defined in Azure for this application, and I've assigned myself to both of them. But when I add [Authorize(Roles="foo")] to a controller, the application redirects to Microsoft to ask for another sign in, and then continues to do that forever. I can't tell whether the roles aren't being passed back in the token, or whether MVC is failing to pick up the roles that are being passed back.
I've tried using KentorOwinCookieSaver but that didn't seem to address the problem.
Is there an additional step I need to take to get MVC to recognize the Azure appRoles? I'm not using Identity Manager or storing any user info in the database.
Here's my Startup.Auth:
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{ CookieSecure = CookieSecureOption.Always,
CookieManager = new Microsoft.Owin.Host.SystemWeb.SystemWebChunkingCookieManager()});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
// If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(signedInUserID));
return authContext.AcquireTokenByAuthorizationCodeAsync(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);
},
AuthenticationFailed = (context) =>
{
context.HandleResponse();
context.Response.Redirect("/Home/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
}
});
}
This turned out to be a ridiculous mistake on my end. After looking at the token again, at Win's prompting, and comparing it to the app registration manifest in the Azure portal, I see the problem. I was trying to use the "displayName" of the role, which is what is displayed on the administration screens, rather than the "value" which is, predictably, the value passed back with the token.
Oops.

Application is not supported over the /common or /consumers endpoints error AD sign in

I hope anyone is able to help me out with this problem
Im trying to get one of the code samples from the Microsoft Graph Api working with a company specific application. After I sign in at my tenant's sign in screen im getting redirected to the application with the following error.
AADSTS90130: Application '{application id}'
(aad name) is not supported over the /common or /consumers
endpoints. Please use the /organizations or tenant-specific endpoint.
In my startup class i've got the following code:
// The graphScopes are the Microsoft Graph permission scopes that are used by this sample: User.Read Mail.Send
private static string appId = ConfigurationManager.AppSettings["ida:AppId"];
private static string appSecret = ConfigurationManager.AppSettings["ida:AppSecret"];
private static string redirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
private static string graphScopes = ConfigurationManager.AppSettings["ida:GraphScopes"];
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// The `Authority` represents the Microsoft v2.0 authentication and authorization service.
// The `Scope` describes the permissions that your app will need. See https://azure.microsoft.com/documentation/articles/active-directory-v2-scopes/
ClientId = appId,
Authority = "https://login.microsoftonline.com/{tenantid}",
PostLogoutRedirectUri = redirectUri,
RedirectUri = redirectUri,
Scope = "openid email profile offline_access " + graphScopes,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
// In a real application you would use IssuerValidator for additional checks,
// like making sure the user's organization has signed up for your app.
// IssuerValidator = (issuer, token, tvp) =>
// {
// if (MyCustomTenantValidation(issuer))
// return issuer;
// else
// throw new SecurityTokenInvalidIssuerException("Invalid issuer");
// },
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async (context) =>
{
var code = context.Code;
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new SessionTokenCache(signedInUserID,
context.OwinContext.Environment["System.Web.HttpContextBase"] as HttpContextBase).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(
appId,
redirectUri,
new ClientCredential(appSecret),
userTokenCache,
null);
string[] scopes = graphScopes.Split(new char[] { ' ' });
AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, scopes);
},
AuthenticationFailed = (context) =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
}
});
}
}
In this code I have the tenant-specific id in the sign in url which works for another application with the same sign-in style.
I'm not sure what is wrong so i'm hoping there is someone who can help me out. I've looked at related questions on here but none seem related to this issue.
You're using the v1 Endpoint to register your application via the Azure Portal and set Multi-tenant to false. This will restrict your application to only AAD users from the tenant at which it's registered.
If you want to accept any AAD user, you'll need to enable multiple tenants. This will allow a report AAD tenant to recognize your application and allow users to authenticate.
If you want to accept both AAD and MSA users, you'll need to register your application at https://apps.dev.microsoft.com. You'll also need to refactor your authentication code to use the v2 Endpoint.

How to set TokenValidationParameters.NameClaimType to "username" instead of "name" for Azure AD B2C user?

I have the following code to configure the process of authentication in my ASP.NET MVC web application, including the setting a claim to the user's "name" when validating the user's identity token received by the application.
(Note that I am following this sample code)
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
CookieSecure = CookieSecureOption.Always
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
// Generate the metadata address using the tenant and policy information
MetadataAddress = String.Format(AadInstance, Tenant, DefaultPolicy),
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = ClientId,
Authority = Authority,
PostLogoutRedirectUri = RedirectUri,
RedirectUri = RedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthenticationFailed = OnAuthenticationFailed,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
},
// Specify the claims to validate
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
},
// Specify the scope by appending all of the scopes requested into one string (separated by a blank space)
Scope = $"{OpenIdConnectScopes.OpenId} {ReadTasksScope} {WriteTasksScope}"
});
}
The "name" claim type maps to the user's DisplayName, which is returned when I use the code User.Identity.Name.
How can I get User.Identity.Name to map to the user's Username, like in the below screenshot of an Azure Active Directory B2C user?
Here is the 2nd half of the answer above, which includes the code changes that were made:
Add the line of code commented with "Added line here" so that the user's ObjectId is claimed:
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
// Extract the code from the response notification
var code = notification.Code;
string signedInUserID = notification.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new MSALSessionCache(signedInUserID, notification.OwinContext.Environment["System.Web.HttpContextBase"] as HttpContextBase).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(ClientId, Authority, RedirectUri, new ClientCredential(ClientSecret), userTokenCache, null);
///////////////////////////////////
// Added line here
///////////////////////////////////
// Add a custom claim to the user's ObjectId ('oid' in the token); Access it with this code: ((System.Security.Claims.ClaimsIdentity)User.Identity).FindFirst("ObjectId").Value
notification.AuthenticationTicket.Identity.AddClaim(new System.Security.Claims.Claim("ObjectId", signedInUserID));
try
{
AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, Scopes);
}
catch (Exception ex)
{
MyLogger.LogTrace("Failed to retrieve AuthenticationResult Token for user " + signedInUserID, MyLogger.LogLevel.Critical);
return;
}
}
Then later in the web application, when you need to get and use the user's ObjectId, do this:
try
{
string signedInUserObjectId = ((System.Security.Claims.ClaimsIdentity)User.Identity).FindFirst("ObjectId").Value;
}
catch (Exception e)
{
... This should never happen, but better safe than sorry ...
}
And lastly, using the Azure AD graph client, you can get the user object using ObjectId, which contains the user name. The specific query you will need is GET https://graph.windows.net/myorganization/users/{user_id}?api-version. You may need to get the UserPrincipalName or a SignInName, depending on your type of user. For more information, see the "Get a user" section here.
I'm not sure how you can get the username into that property as B2C does not return that value.
You can still get this value but it will take more work. B2C does allow you to return the "User's Object ID" which will come back as claim oid. Sample Token.
You can get the oid claim and then query Azure AD to get the username value. See this SO answer on querying Azure AD.
Return User's Object ID
Azure feedback item: include username in JWT claims

Adding SSO OpenId/Azure AD auth to an existing Web Forms app

I have a web forms app currently using either forms authentication (or LDAP which then sets a FormsAuthenticationTicket cookie). I need to add SSO to this project and I'm currently using OpenID/Azure AD to authenticate with. I have the following Startup.cs configured.
public void Configuration(IAppBuilder app)
{
string appId = "<id here>";
string aadInstance = "https://login.microsoftonline.com/{0}";
string tenant = "<tenant here>";
string postLogoutRedirectUri = "https://localhost:21770/";
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = appId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenReceived = context =>
{
System.Diagnostics.Debug.WriteLine("SecurityTokenReceived");
return Task.FromResult(0);
},
SecurityTokenValidated = async n =>
{
var claims_to_exclude = new[]
{
"aud", "iss", "nbf", "exp", "nonce", "iat", "at_hash"
};
var claims_to_keep =
n.AuthenticationTicket.Identity.Claims
.Where(x => false == claims_to_exclude.Contains(x.Type)).ToList();
claims_to_keep.Add(new Claim("id_token", n.ProtocolMessage.IdToken));
if (n.ProtocolMessage.AccessToken != null)
{
claims_to_keep.Add(new Claim("access_token", n.ProtocolMessage.AccessToken));
//var userInfoClient = new UserInfoClient(new Uri("https://localhost:44333/core/connect/userinfo"), n.ProtocolMessage.AccessToken);
//var userInfoResponse = await userInfoClient.GetAsync();
//var userInfoClaims = userInfoResponse.Claims
// .Where(x => x.Item1 != "sub") // filter sub since we're already getting it from id_token
// .Select(x => new Claim(x.Item1, x.Item2));
//claims_to_keep.AddRange(userInfoClaims);
}
var ci = new ClaimsIdentity(
n.AuthenticationTicket.Identity.AuthenticationType,
"name", "role");
ci.AddClaims(claims_to_keep);
n.AuthenticationTicket = new AuthenticationTicket(
ci, n.AuthenticationTicket.Properties
);
},
MessageReceived = context =>
{
System.Diagnostics.Debug.WriteLine("MessageReceived");
return Task.FromResult(0);
},
AuthorizationCodeReceived = context =>
{
System.Diagnostics.Debug.WriteLine("AuthorizationCodeReceived");
return Task.FromResult(0);
},
AuthenticationFailed = context =>
{
System.Diagnostics.Debug.WriteLine("AuthenticationFailed");
context.HandleResponse();
context.Response.Write( context.Exception.Message);
return Task.FromResult(0);
}
,
RedirectToIdentityProvider = (context) =>
{
System.Diagnostics.Debug.WriteLine("RedirectToIdentityProvider");
//string currentUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.Path;
//context.ProtocolMessage.RedirectUri = currentUrl;
return Task.FromResult(0);
}
}
});
app.UseStageMarker(PipelineStage.Authenticate);
}
I have placed this in page Load event of my master (although it never seems to be getting hit - something else must be causing the authentication process to kick off when I navigate to a page requiring authentication.)
if (!Request.IsAuthenticated)
{
HttpContext.Current.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/Login.aspx" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
My Azure settings are all correct because I am hitting SecurityTokenValidated and AuthorizationCodeReceived functions - I can see my email I am logged in with in the claims information, but I am not sure what to do next. As is I have a never ending loop of authentication requests. I am assuming this is because I have not translated the claim information I have received back into forms authentication ? I attempted to add a dummy auth ticket to the response in AuthorizationCodeReceived but that didn't appear to change anything - I am still getting the looping authentication requests.
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1, "<UserName>", DateTime.Now, DateTime.Now.AddMinutes(60), true,"");
String encryptedTicket = FormsAuthentication.Encrypt(authTicket);
context.Response.Cookies.Append(FormsAuthentication.FormsCookieName, encryptedTicket);
This is not a clear cut answer but it's too big for a comment.
I'm using "Organizational Accounts" (i.e. O365 email logins) and I had two big problems (both solved).
First Issue
intermittently, when logging in it would go into an endless redirect loop back and forth between two pages (This didn't happen all the time - only after half an hour testing and logging in and out).
If I left it long enough it would say "query string too long". There is a lot of long winded explanation around cookies and stuff but I had difficulties solving it. In the end it was solved simply by forcing https instead of http
I don't think that's your issue as it seems like it does it everytime. Perhaps have a read through this
New Asp.Net MVC5 project produces an infinite loop to login page
One answer says:
Do not call a protected web API (any web API which requires
Authorization) from an authorization page such as ~/Account/Login
(which, by itself, does NOT do this.). If you do you will enter into
an infinite redirect loop on the server-side.
Second Issue
So the next thing was: our existing authorisation system was sitting in a classic login/pwd table in our database (with an unencrypted password field >:| ). So I needed to pick up the login email and match that to a role defined in this table. Which I did thanks to the guy who answered my question:
Capturing login event so I can cache other user information
This answer meant that I could:
Go pick up the users role from the database once upon initial login
Save this role inside the existing native C# security object
Best of all: use the native authorisation annotations in my controller methods without any custom code in the method
I think thats what you are after but the question really is: how are you currently storing roles? In a database table? In Active Directory? In Azure active directory?
So in the hope that it help someone else - this is what I ended up with. In the web.config, the authentication mode is set to 'Forms'. I added the following Startup.cs
public class Startup
{
public void Configuration(IAppBuilder app)
{
var appId = ConfigurationCache.GetConfigurationString(TOS_Configuration.KEY_SSO_APPID);
var authority = ConfigurationCache.GetConfigurationString(TOS_Configuration.KEY_SSO_AUTHORITY);
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = appId,
Authority = authority,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = context =>
{
string username = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.Name).Value;
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1, username, DateTime.Now, DateTime.Now.AddMinutes(60), true, "");
String encryptedTicket = FormsAuthentication.Encrypt(authTicket);
context.Response.Cookies.Append(FormsAuthentication.FormsCookieName, encryptedTicket);
return Task.FromResult(0);
},
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Write(context.Exception.Message);
return Task.FromResult(0);
}
}
});
// This makes any middleware defined above this line run before the Authorization rule is applied in web.config
app.UseStageMarker(PipelineStage.Authenticate);
}
}
I did not add any challenge to my site master pages and instead added the following to my login page to trigger the authentication challenge:
if (!Request.IsAuthenticated && AttemptSSO)
{
ReturnURL = Request.QueryString["ReturnUrl"];
HttpContext.Current.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/Login.aspx" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
else if (Request.IsAuthenticated && AttemptSSO)
{
if (!string.IsNullOrEmpty(ReturnURL))
{
var url = ReturnURL;
ReturnURL = "";
Response.Redirect(ResolveUrl(url));
}
else
{
Response.Redirect(ResolveUrl("~/Default.aspx"));
}
}
This means that if a user arrives at a authenticated page without a valid forms authentication token they get redirected to the login page. The login page takes care of deciding if SSO is set up and handling it appropriately. If anyone has any thoughts as to how to improve it - I'd love to hear them, but for the moment this does work.

Azure AD B2C - Role management [duplicate]

This question already has answers here:
Authorize By Group in Azure Active Directory B2C
(8 answers)
Closed last year.
I have an Asp.NET MVC Application connected with Azure AD B2C.
In the Administrator settings I've created an Administrators Group:
In my code I would like to use [Authorize(Roles = "Administrator")]
With regular Azure Active Directory it was easy to add (just 3 lines of code). But for the Azure AD B2C I cannot find any tutorial or example in the web which is working. Maybe you can tell me what i need to modify.
Here is the ConfigureAuth method of my Startup.Auth.cs
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Generate the metadata address using the tenant and policy information
MetadataAddress = String.Format(AadInstance, Tenant, DefaultPolicy),
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = ClientId,
RedirectUri = RedirectUri,
PostLogoutRedirectUri = RedirectUri,
// Specify the callbacks for each type of notifications
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
},
// Specify the claims to validate
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
},
// Specify the scope by appending all of the scopes requested into one string (separated by a blank space)
Scope = $"openid profile offline_access {ReadTasksScope} {WriteTasksScope}"
}
);
}
Azure AD B2C does not yet include Group claims in the token it sends to the application thus you can't follow the same approach as you outlined with Azure AD (which does include group claims in the token).
You can support this feature ask by voting for it in the Azure AD B2C feedback forum: Get user membership groups in the claims with Azure AD B2C
That being said, you can do some extra work in this application to have it manually retrieve these claims the group claims and inject them into the token.
First, register a separate application that'll call the Microsoft Graph to retrieve the group claims.
Go to https://apps.dev.microsoft.com
Create an app with Application Permissions : Directory.Read.All.
Add an application secret by clicking on Generate new password
Add a Platform and select Web and give it any redirect URI, (e.g. https://yourtenant.onmicrosoft.com/groups)
Consent to this application by navigating to: https://login.microsoftonline.com/YOUR_TENANT.onmicrosoft.com/adminconsent?client_id=YOUR_CLIENT_ID&state=12345&redirect_uri=YOUR_REDIRECT_URI
Then, you'll need to add code the following code inside of the OnAuthorizationCodeReceived handler, right after redeeming the code:
var authority = $"https://login.microsoftonline.com/{Tenant}";
var graphCca = new ConfidentialClientApplication(GraphClientId, authority, GraphRedirectUri, new ClientCredential(GraphClientSecret), userTokenCache, null);
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
try
{
AuthenticationResult authenticationResult = await graphCca.AcquireTokenForClientAsync(scopes);
string token = authenticationResult.AccessToken;
using (var client = new HttpClient())
{
string requestUrl = $"https://graph.microsoft.com/v1.0/users/{signedInUserID}/memberOf?$select=displayName";
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
HttpResponseMessage response = await client.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
var json = JObject.Parse(responseString);
foreach (var group in json["value"])
notification.AuthenticationTicket.Identity.AddClaim(new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Role, group["displayName"].ToString(), System.Security.Claims.ClaimValueTypes.String, "Graph"));
//TODO: Handle paging.
// https://developer.microsoft.com/en-us/graph/docs/concepts/paging
// If the user is a member of more than 100 groups,
// you'll need to retrieve the next page of results.
}
} catch (Exception ex)
{
//TODO: Handle
throw;
}

Categories

Resources