Cannot get role from logged-in Azure AD B2C user - oauth-2.0

I am attempting to use the following to determine if an Azure AD B2C logged-in user is an Administrator:
if (User.IsInRole("Administrator"))
{
.... Display special info for Admins ....
}
However, when I look into the System.Security.Principal.IPrincipal.User object, I see null for the list of roles that this user has:
The following is the relevant code that configures authentication and requests TokenValidationParameters, including for the roles to be validated. I've tried the following: RoleClaimType = "role" and RoleClaimType = "roles", both of which haven't worked for me.
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseKentorOwinCookieSaver();
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
},
/////////// HERE //////////
// Specify the claims to validate
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role",
},
// Specify the scope by appending all of the scopes requested into one string (seperated by a blank space)
Scope = $"{OpenIdConnectScopes.OpenId} {ReadTasksScope} {WriteTasksScope}"
}
);
}
However, when I decode the id_token retrieved from the authentication process and decode it using the tool https://jwt.ms/, I don't see a "roles" claim, as shown in the screenshot.
Furthermore, in the SignIn Azure AD B2C policy, perhaps I need to add a "roles" ClaimType?
Please help! What else do I need to do in order to get User.IsInRole("Administrator") to work? Thank you!

To solve this, I ended up using the Azure AD Graph Client to query for all of the directory roles belonging to a user with a specified objectId. Here is the method I added:
public async Task<string> GetUserRoleByObjectId(string objectId)
{
return await SendGraphGetRequest("/users/" + objectId + "/$links/memberOf", null);
}
I added this method to the B2CGraphClient.cs file in the following sample code, which I integrated into my web app: https://github.com/AzureADQuickStarts/B2C-GraphAPI-DotNet

Related

Authorize with Controllers with AD Groups using OIDC

we have a web application which uses OIDC for single sign on to authenticate with
Azure AD. The single sign on works great, Users are able to sign in with their AD accounts. The token it returns also contains AD groups.
I would like to authorize my MVC controllers to only allow certain groups to use certain controllers.
How do I implement this? I can see the groups are being sent back in the token represented as GUIDs.
I have tried setting the role claims via RoleClaimType = "roles", but this doesn't work.
here is my code.
public void ConfigureAuth(IAppBuilder app)
{
FATContext db = new FATContext();
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
CookieManager = new Microsoft.Owin.Host.SystemWeb.SystemWebChunkingCookieManager(),
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
// Set claimsPrincipal's roles to the roles claim
RoleClaimType = "roles",
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
RedirectToIdentityProvider = (context) =>
{
context.ProtocolMessage.RedirectUri = HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path);
context.ProtocolMessage.PostLogoutRedirectUri = new UrlHelper(HttpContext.Current.Request.RequestContext).Action("Index", "Home", null, HttpContext.Current.Request.Url.Scheme);
return Task.FromResult(0);
},
// 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 ADALTokenCache(signedInUserID));
return authContext.AcquireTokenByAuthorizationCodeAsync(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);
}
}
});
}
So for example, how can I get this to work with my controller such as
[AuthorizeUser(Roles = "xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")]
public ActionResult Index()
{
return View();
}
or to check is a user is in a certain role i.e User.IsInRole("example ")
In Azure AD we can define application roles and assign the roles to the groups.
Now the users of this group will have a claim with the roles defined(example as shown below). Refer this link for the detailed documentation on how to create/manage roles for an application in Azure AD.
{
"roles": ["admin"]
}
If you are just trying to use groups-based authorization you can use RoleClaimType = "groups" and the below example codes in controller or in the action methods. For more details you have similar information here
[Authorize(Roles = "Admin")]
public class TestAdminAccessController : ApiController
{
public ActionResult Index()
{
if (this.User.Identity.IsAuthenticated)
{
if (User.IsInRole("Admin"))
{
// Your logic
}
else
{
// is NOT an admin. No permissions
}
}
else
{
// is NOT Authenticated;
}
}
}

Mixing cookie external login using Azure AD and individual account in MVC5

I am getting problems with application cookie and external cookie that are integrated login with Azure AD to my web app using MVC5. Currently, my local account work correctly but external account (Google and Azure AD) cannot map external cookie to local cookie. My code get userId return incorrect user Id.
IIdentity ident = HttpContext.Current.GetOwinContext().Request.User.Identity;
ident.GetUserId()
Below is my startup.cs
public partial class Startup
{
// The Client ID is used by the application to uniquely identify itself to Azure AD.
string clientId = System.Configuration.ConfigurationManager.AppSettings["ClientId"];
// RedirectUri is the URL where the user will be redirected to after they sign in.
string redirectUri = System.Configuration.ConfigurationManager.AppSettings["RedirectUri"];
string postLogoutRedirectUri = System.Configuration.ConfigurationManager.AppSettings["PostLogoutRedirectUri"];
// Tenant is the tenant ID (e.g. contoso.onmicrosoft.com, or 'common' for multi-tenant)
static string tenant = System.Configuration.ConfigurationManager.AppSettings["Tenant"];
// Authority is the URL for authority, composed by Microsoft identity platform endpoint and the tenant name (e.g. https://login.microsoftonline.com/contoso.onmicrosoft.com/v2.0)
string authority = String.Format(System.Globalization.CultureInfo.InvariantCulture, System.Configuration.ConfigurationManager.AppSettings["Authority"], tenant);
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(AppIdentityDbContext.Create);
app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);
app.CreatePerOwinContext<AppSignInManager>(AppSignInManager.Create);
app.CreatePerOwinContext<AppRoleManager>(AppRoleManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
AuthenticationMode = AuthenticationMode.Active,
LoginPath = new PathString("/"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<AppUserManager, AppUser>(
validateInterval: TimeSpan.FromHours(1),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
},
ExpireTimeSpan = TimeSpan.FromHours(1),
//Samesite secure
CookieSameSite = SameSiteMode.Lax,
CookieHttpOnly = true,
CookieSecure = CookieSecureOption.Always,
CookieManager = new SameSiteCookieManager(new SystemWebCookieManager())
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
//Open Id Connect
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ExternalCookie,
CookieManager = new SameSiteCookieManager(new SystemWebCookieManager())
});
app.UseOpenIdConnectAuthentication(CreateOpenIdOptions());
// GOOGLE
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
{
ClientId = ConfigurationManager.AppSettings["GoogleClientID"].ToString(),
ClientSecret = ConfigurationManager.AppSettings["GoogleClientSecret"].ToString()
});
}
private OpenIdConnectAuthenticationOptions CreateOpenIdOptions()
{
var options = new OpenIdConnectAuthenticationOptions
{
Authority = authority,
ClientId = clientId,
RedirectUri = redirectUri,
AuthenticationMode = AuthenticationMode.Passive,
// PostLogoutRedirectUri is the page that users will be redirected to after sign-out. In this case, it is using the home page
PostLogoutRedirectUri = postLogoutRedirectUri,
Scope = OpenIdConnectScope.OpenIdProfile, // a basic set of permissions for user sign in & profile access
// ResponseType is set to request the id_token - which contains basic information about the signed-in user
ResponseType = OpenIdConnectResponseType.IdToken,
TokenValidationParameters = new TokenValidationParameters
{
// In a real application you would use ValidateIssuer = true for additional checks and security.
ValidateIssuer = false,
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthenticationFailed = OnAuthenticationFailed,
},
// Handling SameSite cookie according to https://learn.microsoft.com/en-us/aspnet/samesite/owin-samesite
CookieManager = new SameSiteCookieManager(
new SystemWebCookieManager()),
};
return options;
}
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
// Handle any unexpected errors during sign in
context.OwinContext.Response.Redirect("/Error?message=" + context.Exception.Message);
context.HandleResponse(); // Suppress the exception
return Task.FromResult(0);
}
}
Below is sign out method which is called before sign in
var authenticationTypes = new string[] {
DefaultAuthenticationTypes.ApplicationCookie,
DefaultAuthenticationTypes.ExternalCookie,
};
AuthManager.SignOut(authenticationTypes);
I also already tried apply many fixed posts related to this but it does not work. How can we resolve external cookie map to local cookie?
Finally, I found workaround solutions below:
First if you don want to use Open id connect use the link below
Use Azure AD only for Authentication and not Authorization
I use Kentor.OwinCookieSaver to resolve my problem
https://github.com/Sustainsys/owin-cookie-saver

Azure B2C authentication redirects locally, but not when hosted on Azure

I'm new to Azure AD B2C. I'm trying to set up azure B2C authentication for an MVC application.
The login works fine locally, but when it's not working on server.
The application is hosted on Azure AD.
I don't know if I missed something!! Can someone please help?
private OpenIdConnectAuthenticationOptions CreateOptionsFromPolicy(string policy)
{
return new OpenIdConnectAuthenticationOptions
{
// set the authentication type to the id of the policy
MetadataAddress = metaDataAddress,
AuthenticationType = policy,
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = clientId,
//ClientSecret = clientSecret,
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = AuthenticationFailed,
},
Scope = "openid",
ResponseType = "id_token",
// used for displaying the user's name in the navigation bar.
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role",
SaveSigninToken = true
}
};
}
When it is deployed on to the server, after sign in, it is not returning to the application. Instead the page seems to blink and in between I can see something displayed as "As part of the authentication process the page is displayed several times, please click the button to continue"..
Startup.cs file seems alright to me.Couple things to cross check:
redirectUri matches with the website
Here is my signup and sign in action method:
public void SignIn()
{
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = "/" }, Startup.SignInPolicyId);
}
}
public void SignUp()
{
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = "/" }, Startup.SignUpPolicyId);
}
}
I have followed below github repo and it worked for me. Try this and see if it works
https://github.com/tjoudeh/Azure-Active-Directory-B2C/tree/master/AADB2C.WebClientMvc/Controllers
Reference:
http://bitoftech.net/2016/08/31/integrate-azure-ad-b2c-asp-net-mvc-web-app/
Please provide the detailed error with entire code base will try to reproduce at my end.

Owin OpenId Callback url

I'm working in implementing OpenId in a .NET MVC application.
I have used: https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-v1-aspnet-webapp
as a starting guide. It is working, but i have 1 question regarding RedirectUri and CallbackPath.
If i only use RedirectUri, the callback page in my application gets a 302 redirection.
If i use CallbackPath the callback page is actually hit.
It is not really clear from the example what is going on? This is from MS:
"An optional constrained path on which to process the authentication callback. If not provided and RedirectUri is available, this value will be generated from RedirectUri."
I'm using [Authorize] attribute on my controllers.
Code Startup.cs:
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
var openIdOptions = new OpenIdConnectAuthenticationOptions
{
// Sets the ClientId, authority, RedirectUri as obtained from web.config
ClientId = ApplicationIdentifier,
Authority = FederationGateway,
RedirectUri = RedirectUrl,
ClientSecret = AppToken,
AuthenticationMode = AuthenticationMode.Active,
//CallbackPath = new PathString("/callback/"),
// PostLogoutRedirectUri is the page that users will be redirected to after sign-out. In this case, it is using the home page
PostLogoutRedirectUri = "~/home/loggedout/",
//Scope is the requested scope: OpenIdConnectScopes.OpenIdProfileis equivalent to the string 'openid profile': in the consent screen, this will result in 'Sign you in and read your profile'
Scope = OpenIdConnectScope.OpenIdProfile,
// ResponseType is set to request the id_token - which contains basic information about the signed-in user
////ResponseType = OpenIdConnectResponseType.IdToken,
ResponseType = OpenIdConnectResponseType.IdTokenToken,
// ValidateIssuer set to false to allow work accounts from any organization to sign in to your application
// To only allow users from a single organizations, set ValidateIssuer to true and 'tenant' setting in web.config to the tenant name or Id (example: contoso.onmicrosoft.com)
// To allow users from only a list of specific organizations, set ValidateIssuer to true and use ValidIssuers parameter
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = false
},
// OpenIdConnectAuthenticationNotifications configures OWIN to send notification of failed authentications to OnAuthenticationFailed method
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
SecurityTokenValidated = OnSecurityTokenValidated
}
};
app.UseOpenIdConnectAuthentication(openIdOptions);
AntiForgeryConfig.UniqueClaimTypeIdentifier = IdentityNameIdentifier;
}

Azure AD B2C Token returns name but User.Identity.Name is null

I have an Azure AD B2C token that seems to be correctly returning the currently logged-in user's name. Here is a screenshot from jwt.ms which I am using to decode the token returned by the application after I have logged in:
However, then I attempt to use #User.Identity.Name in my _Layout.cshtml. Why is it null? Shouldn't it be equal to the "name" value in the screenshot?
It turned out I was missing the line marked by the comments:
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,
},
//////// WAS MISSING THIS BELOW /////////
// Specify the claims to validate
TokenValidationParameters = new TokenValidationParameters
{
// This claim is in the Azure AD B2C token; this code tells the web app to "absorb" the token "name" and place it in the user object
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 entire file is located here: https://github.com/Azure-Samples/active-directory-b2c-dotnet-webapp-and-webapi/blob/master/TaskWebApp/App_Start/Startup.Auth.cs
See this working example that is using Owin (which it sounds like you're using).
<ul class="nav navbar-nav navbar-right">
<li>
<a id="profile-link">#User.Identity.Name</a>
...
</li>
</ul>
Source
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
...
);
}
Source

Resources