I'm having an issue using Azure AAD appRoles and MVC, i have modified the manifest added a few roles and assigned them to a couple of users.
However when i try using either User.IsInRole or ClaimsPrincipal.Current.IsInRole it always returns false.
Click Here to see
The role is being return in the json of Claims in the screenshot above {roles:SuperAdmin}.
I have done alot of reading up and as far as i can see i am doing everything correctly but cant find a reason why?
Below is my Startup.Auth.cs
public partial class Startup
{
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string appKey = ConfigurationManager.AppSettings["ida:ClientSecret"];
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string tenantId = ConfigurationManager.AppSettings["ida:TenantId"];
private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
public static readonly string Authority = aadInstance + tenantId;
// This is the resource ID of the AAD Graph API. We'll need this to request a token to call the Graph API.
//string graphResourceId = "https://graph.windows.net";
public void ConfigureAuth(IAppBuilder app)
{
ApplicationDbContext db = new ApplicationDbContext();
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
RoleClaimType= "roles"
},
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 ADALTokenCache(signedInUserID));
return Task.FromResult(0);
}
}
});
}
}
Since you are using OpenID Connect Owin middleware to sign-in users from Azure AD , you doesn't need to enable App Service Authentication / Authorization feature , that feature provides a way for your application to sign in users so that you don't have to change code on the app backend. Just turn off the App Service Authentication / Authorization feature .
Related
Currently I'm implementing Microsoft Authentication Library(MSAL) on my C# .NET framework webapp (single tenant) and when I acquire the token using the code from Owin I'm getting the wrong GUID for the user/tenant in my confidential app.
This is the return from the confidential app(dc3... is the UserId and cf3.. is the TenantId), this is from a different directory on Azure.
But the claims generated by C# have the correct values:
If I check the object from the confidential app I can see inside "TenantProfiles" the same values as the above (f81 and e24), the correct ones.
But since the Claims have different values as the Confidential App, I cannot get the user with GetAccountAsync(), because it tries to find a user based on "dc3" GUID not "f81" GUID. I can get the user using a filter on GetAccountsAsync(), but this method is deprecated.
Here's my code
public static string appKey = ConfigurationManager.AppSettings["ida:AppKey"];
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"]; //https://login.microsoftonline.com/{0}
public static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
public static string redirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
public static readonly string Authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant) + "/v2.0";
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions() { CookieSecure = CookieSecureOption.SameAsRequest });
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
Authority = Startup.Authority,
ClientId = Startup.clientId,
RedirectUri = Startup.redirectUri,
PostLogoutRedirectUri = Startup.redirectUri,
Scope = OpenIdConnectScopes.OpenIdProfile,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthorizationFailed
}
});
}
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
var app = IdentityApiUtility.BuildConfidentialClientApplication();
var result = await app.AcquireTokenByAuthorizationCode(new[] { "https://graph.microsoft.com/.default" }, notification.Code).ExecuteAsync();
}
and
public static IConfidentialClientApplication BuildConfidentialClientApplication()
{
if (clientapp == null)
{
clientapp = ConfidentialClientApplicationBuilder
.Create(Startup.clientId)
.WithClientSecret(Startup.appKey)
.WithRedirectUri(Startup.redirectUri)
.WithAuthority(Startup.Authority)
.Build();
}
return clientapp;
}
/// <summary>
/// Gets an auth code on behalf of the current user
/// </summary>
private AuthenticationResult GetOpenIdConnectAuth()
{
try
{
string userObjectID = $"{ClaimsPrincipal.Current.GetObjectId()}.{ClaimsPrincipal.Current.GetTenantId()}";
var app = BuildConfidentialClientApplication();
var scopes = new[] { "https://graph.microsoft.com/.default" };
//The userObjectId here starts with f81, which I got from the claims. But the user in the ConfidentialApp starts with dc3 which from another Azure Directory
var account = app.GetAccountAsync(userObjectID).Result;
var accessToken = app.AcquireTokenSilent(scopes, account).ExecuteAsync().Result;
return accessToken;
}
catch (Exception ex)
{
throw new Exception("Authentication Error in GetOpenIdConnectAuth method");
}
}
I already checked clientid/secret/tenant multiple times just to be sure that I wasn't sending the wrong authority/tenant and this is not the case. Does anyone have a suggestion how I can get the user from the ConfidentialApp or what I'm doing wrong?
I'm using OWIN combined with Azure Active Directory App Registration as my authentication method on my MVC Web App as below to restrict the login user within a single domain. This part is functioning well.
public partial class Startup
{
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Home/ErrorPage?message=" + context.Exception.Message);
return Task.FromResult(0);
}
}
});
}
}
Now I want to use another Azure Active Directory App Registration to restrict few users to only one admin configuration page. I don`t know if it doable and how to do it.
This is current controller attribute code to redirect user to a username/password login page before accessing admin configuration page. How can I change it to be redirected to an AAD login. In this way, I can configure the qualified user in AAD without maintain any username and password.
public class ConfigLoginAttribute : AuthorizeAttribute
{
public bool Ignore = true;
public ConfigLoginAttribute(bool ignore = true)
{
Ignore = ignore;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (Ignore == false)
{
return;
}
if (CookieHelper.GetCookie("username") == "")
{
filterContext.Result = new RedirectToRouteResult("Default", new RouteValueDictionary { { "Action", "AdminLogin" }, { "Controller", "Config" } });
return;
}
}
}
I`m new to this area and even English. Hopefully I explained it clear.
Thank you guys so much in advance.
How to refresh Authentication token for
Microsoft Graph using Microsoft Graph .NET Client Library or other using C#?
What I am currently doing is keeping token in the static class:
public class TokenKeeper
{
public static string token = null;
public static string AcquireToken()
{
if (token == null || token.IsEmpty())
{
throw new Exception("Authorization Required.");
}
return token;
}
public static void Clear()
{
token = null;
}
}
I fill in the token in Startup class:
public partial class Startup
{
private static string AppKey = CloudConfigurationManager.GetSetting("ida:Password");
private static string aadInstance = CloudConfigurationManager.GetSetting("ida:AADInstance");
private static string TenantName = CloudConfigurationManager.GetSetting("ida:Tenant");
private static string Authority = String.Format(CultureInfo.InvariantCulture, aadInstance, TenantName);
private static string graphResourceId = CloudConfigurationManager.GetSetting("ida:GraphUrl");
private BpContext db = new BpContext();
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
public void ConfigureAuth(IAppBuilder app)
{
string ClientId = CloudConfigurationManager.GetSetting("ida:ClientID");
string Authority = "https://login.microsoftonline.com/common/";
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = ClientId,
Authority = Authority,
Scope = "User.ReadBasic.All",
//Details omitted
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
// Create a Client Credential Using an Application Key
ClientCredential credential = new ClientCredential(ClientId, AppKey);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst(
"http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);
TokenKeeper.token = result.AccessToken;
return Task.FromResult(0);
}
//Details omitted
}
});
}
}
I also clear the token on Sign Out.
The AuthenticationResult object contains both access token and refresh token. So, the refresh token can also be persisted in TokenKeeper similar to access token. When access token expires (indicated by AuthenticationResult.ExpiresOn), use the refresh token with AuthenticationContext.AcquireTokenByRefreshToken method to get new access token.
If you don't want to track refresh tokens explicitly, please refer to ADAL Cache to know how ADAL library can do it for you.
You can refresh access token by providing RefreshToken which you received alongside AccessToken. Since you have ID/Secret available in you code you can use them to provide ClientCredential.
Code example would be:
var authContext = new AuthenticationContext("https://login.microsoftonline.com/common");
var result = authContext.AcquireTokenByRefreshToken(refreshToken, new ClientCredential(ClientId, AppKey));
What I'm trying to do is accessing user claims which returns from ADFS login. ADFS returns username and with that username I have to run a query to another DB to get user information and store it. I don't really know where to do that and what the best practice is. I can access user claims in the view controller like:
public ActionResult Index()
{
var ctx = Request.GetOwinContext();
ClaimsPrincipal user = ctx.Authentication.User;
IEnumerable<Claim> claims = user.Claims;
return View();
}
But what I need to do is as I said access claims like in global.asax.cs or startup.cs to store user information before the application runs.
This is my Startup.Auth.cs file:
public partial class Startup
{
private static string realm = ConfigurationManager.AppSettings["ida:Wtrealm"];
private static string adfsMetadata = ConfigurationManager.AppSettings["ida:ADFSMetadata"];
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(WsFederationAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(
new CookieAuthenticationOptions
{
AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType
});
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
Wtrealm = realm,
MetadataAddress = adfsMetadata
});
}
}
We add an event handler to the WsFederationAuthenticationOptions value in our startup file.
This happens immediately after the security token has been validated.
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions()
{
MetadataAddress = MetadataAddress,
Wtrealm = Wtrealm,
Wreply = CallbackPath,
Notifications = new WsFederationAuthenticationNotifications()
{
SecurityTokenValidated = (ctx) =>
{
ClaimsIdentity identity = ctx.AuthenticationTicket.Identity;
DoSomethingWithLoggedInUser(identity);
}
}
};
I have an asp.net mvc application with azure AAD sign in.
When I press f5 to debug the application goes to azure to authenticate in AAD, then it goes back to the application to the controller, and its redirected back again to azure.
I know this because If I put a breakpoint on the Sign In controller it gets hit infinitely
This is my route config
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//routes.IgnoreRoute("");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Dashboards", action = "Dashboard_1", id = UrlParameter.Optional }
);
}
This is my dashboard controller which has authorize
[Authorize]
public class DashboardsController : Controller
{
public ActionResult Dashboard_1()
{
return View();
}
This is my Sign In and sign account controller actions
public class AccountController : Controller
{
public void SignIn()
{
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
public void SignOut()
{
// Remove all cache entries for this user and send an OpenID Connect sign-out request.
string usrObjectId = ClaimsPrincipal.Current.FindFirst(SettingsHelper.ClaimTypeObjectIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(SettingsHelper.AzureADAuthority, new EfAdalTokenCache(usrObjectId));
authContext.TokenCache.Clear();
HttpContext.GetOwinContext().Authentication.SignOut(
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
}
public ActionResult ConsentApp()
{
string strResource = Request.QueryString["resource"];
string strRedirectController = Request.QueryString["redirect"];
string authorizationRequest = String.Format(
"{0}oauth2/authorize?response_type=code&client_id={1}&resource={2}&redirect_uri={3}",
Uri.EscapeDataString(SettingsHelper.AzureADAuthority),
Uri.EscapeDataString(SettingsHelper.ClientId),
Uri.EscapeDataString(strResource),
Uri.EscapeDataString(String.Format("{0}/{1}", this.Request.Url.GetLeftPart(UriPartial.Authority), strRedirectController))
);
return new RedirectResult(authorizationRequest);
}
public ActionResult AdminConsentApp()
{
string strResource = Request.QueryString["resource"];
string strRedirectController = Request.QueryString["redirect"];
string authorizationRequest = String.Format(
"{0}oauth2/authorize?response_type=code&client_id={1}&resource={2}&redirect_uri={3}&prompt={4}",
Uri.EscapeDataString(SettingsHelper.AzureADAuthority),
Uri.EscapeDataString(SettingsHelper.ClientId),
Uri.EscapeDataString(strResource),
Uri.EscapeDataString(String.Format("{0}/{1}", this.Request.Url.GetLeftPart(UriPartial.Authority), strRedirectController)),
Uri.EscapeDataString("admin_consent")
);
return new RedirectResult(authorizationRequest);
}
public void RefreshSession()
{
string strRedirectController = Request.QueryString["redirect"];
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = String.Format("/{0}", strRedirectController) }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
and this is my startup.auth.cs
public void ConfigureAuth(IAppBuilder app)
{
// configure the authentication type & settings
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
// configure the OWIN OpenId Connect options
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = SettingsHelper.ClientId,
Authority = SettingsHelper.AzureADAuthority,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
// when an auth code is received...
AuthorizationCodeReceived = (context) => {
// get the OpenID Connect code passed from Azure AD on successful auth
string code = context.Code;
// create the app credentials & get reference to the user
ClientCredential creds = new ClientCredential(SettingsHelper.ClientId, SettingsHelper.ClientSecret);
string userObjectId = context.AuthenticationTicket.Identity.FindFirst(System.IdentityModel.Claims.ClaimTypes.NameIdentifier).Value;
// use the ADAL to obtain access token & refresh token...
// save those in a persistent store...
EfAdalTokenCache sampleCache = new EfAdalTokenCache(userObjectId);
AuthenticationContext authContext = new AuthenticationContext(SettingsHelper.AzureADAuthority, sampleCache);
// obtain access token for the AzureAD graph
Uri redirectUri = new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path));
AuthenticationResult authResult = authContext.AcquireTokenByAuthorizationCode(code, redirectUri, creds, SettingsHelper.AzureAdGraphResourceId);
// successful auth
return Task.FromResult(0);
},
AuthenticationFailed = (context) => {
context.HandleResponse();
return Task.FromResult(0);
}
},
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = false
}
});
}
We ran into the same issue and solved it by slipping in the Kentor cookie saver. See https://github.com/KentorIT/owin-cookie-saver for details.
To resolve this issue: you can upgrade your application to use ASP.NET Core. If you must continue stay on ASP.NET, perform the following:
Update your application’s Microsoft.Owin.Host.SystemWeb package be at least version and Modify your code to use one of the new cookie manager classes, for example something like the following:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
CookieManager = new Microsoft.Owin.Host.SystemWeb.SystemWebChunkingCookieManager()
});
Reference Link