B2C reset password - azure-ad-b2c

I have following in my startup:
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 redirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
// B2C policy identifiers
// public static string SignUpPolicyId = ConfigurationManager.AppSettings["ida:SignUpPolicyId"];
public static string SignInUpPolicyId = ConfigurationManager.AppSettings["ida:SignInUpPolicyId"];
public static string DefaultPolicy = SignInUpPolicyId;
public static string ResetPasswordPolicyId = ConfigurationManager.AppSettings["ida:ResetPasswordPolicyId"];
public static string ProfilePolicyId = ConfigurationManager.AppSettings["ida:UserProfilePolicyId"];
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
// Configure OpenID Connect middleware for each policy
app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(SignInUpPolicyId));
//app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(ResetPasswordPolicyId));
// app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(SignInPolicyId));
}
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
notification.HandleResponse();
// Handle the error code that Azure AD B2C throws when trying to reset a password from the login page
// because password reset is not supported by a "sign-up or sign-in policy"
if (notification.ProtocolMessage.ErrorDescription != null && notification.ProtocolMessage.ErrorDescription.Contains("AADB2C90118"))
{
// If the user clicked the reset password link, redirect to the reset password route
notification.Response.Redirect("/Account/ResetPassword");
}
else if (notification.Exception.Message == "access_denied")
{
notification.Response.Redirect("/");
}
else
{
notification.Response.Redirect("/Home/Error?message=" + notification.Exception.Message);
}
return Task.FromResult(0);
}
private Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
var policy = notification.OwinContext.Get<string>("Policy");
if (!string.IsNullOrEmpty(policy) && !policy.Equals(DefaultPolicy))
{
//notification.ProtocolMessage.Scope = OpenIdConnectScopes.OpenId;
//notification.ProtocolMessage.ResponseType = OpenIdConnectResponseTypes.IdToken;
notification.ProtocolMessage.IssuerAddress = notification.ProtocolMessage.IssuerAddress.Replace(DefaultPolicy, policy);
}
return Task.FromResult(0);
}
private OpenIdConnectAuthenticationOptions CreateOptionsFromPolicy(string policy)
{
return new OpenIdConnectAuthenticationOptions
{
// For each policy, give OWIN the policy-specific metadata address, and
// set the authentication type to the id of the policy
MetadataAddress = String.Format(aadInstance, tenant, policy),
AuthenticationType = policy,
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = clientId,
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed
},
Scope = "openid",
ResponseType = "id_token",
// This piece is optional - it is used for displaying the user's name in the navigation bar.
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
SaveSigninToken = true //important to save the token in boostrapcontext
}
};
}
}
In my Controller I have following:
public void ResetPassword()
{
// Let the middleware know you are trying to use the reset password
policy (see OnRedirectToIdentityProvider in Startup.Auth.cs)
HttpContext.GetOwinContext().Set("Policy", Startup.ResetPasswordPolicyId);
// Set the page to redirect to after changing passwords
var authenticationProperties = new AuthenticationProperties { RedirectUri = "/" };
HttpContext.GetOwinContext().Authentication.Challenge(authenticationProperties);
}
Right now the reset password just redirect to "/". I am not sure how this works, and havent been able to find any samples. I tried following this sample but it uses some cores libraries and havent succeeded using the documentation.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-devquickstarts-web-dotnet-susi
After following Chris suggestion it worked. See image below when clicking reset password.

Follow this Startup.Auth.cs file as closely as possible to get yourself started.
The ConfigureAuth method of the Startup class registers the OWIN OpenID Connect middleware that enables an ASP.NET MVC controller to set the Azure AD B2C policy, to be redirected to, using the OWIN context.
Example:
HttpContext.GetOwinContext().Set("Policy", Startup.ResetPasswordPolicyId);

Related

Usage of the /common endpoint is not supported for such applications created after '10/15/2018' issue

Similar issue here. I have checked the answer and try to implement all the possible forms of link in my startup.cs class with the following code:
var idClient = ConfidentialClientApplicationBuilder.Create(appId)
.WithRedirectUri(redirectUri)
.WithTenantId(tenantId)
.WithClientSecret(appSecret)
.WithAuthority(Authority) // Authority contains the link as mentioned in the page(link attached above)
.Build();
I still get the similar error:
"OpenIdConnectMessage.Error was not null, indicating an error. Error: 'invalid_request'. Error_Description (may be empty): 'AADSTS50194: Application 'xxx-xxx-xxx-xxx-xxxx'(ASPNET-Quickstart) is not configured as a multi-tenant application. Usage of the /common endpoint is not supported for such applications created after '10/15/2018'. Use a tenant-specific endpoint or configure the application to be multi-tenant.
Trace ID: xxx-xxx-xxx-xxx-xxxx
Correlation ID: xxx-xxx-xxx-xxx-xxxx
Timestamp: 2022-06-11 05:33:24Z'. Error_Uri (may be empty): 'error_uri is null'."
The combination of links I have used in variable Authority are the following: "https://login.microsoftonline.com/MY_TENANT_NAME" and "https://login.microsoftonline.com/MY_TENANT_ID"
I am being redirect to login page but after entering credentials OnAuthenticationFailedAsync method is being executed. This is the code of my startup class:
[assembly: OwinStartup(typeof(Web.Startup))]
namespace Web
{
public partial class Startup
{
// Load configuration settings from PrivateSettings.config
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:AppScopes"];
private static string tenantId = ConfigurationManager.AppSettings["ida:tenantId"];
private static string aadInstance = EnsureTrailingSlash(ConfigurationManager.AppSettings["ida:AADInstance"]);
public static string Authority = "https://graph.microsoft.com/"+ tenantId;
string graphResourceId = "https://graph.microsoft.com/";
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=316888
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = appId,
Authority = "https://login.microsoftonline.com/common/v2.0",
Scope = $"openid email profile offline_access {graphScopes}",
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
TokenValidationParameters = new TokenValidationParameters
{
// For demo purposes only, see below
ValidateIssuer = true
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailedAsync,
AuthorizationCodeReceived = OnAuthorizationCodeReceivedAsync
}
}
);
}
private static Task OnAuthenticationFailedAsync(AuthenticationFailedNotification<OpenIdConnectMessage,
OpenIdConnectAuthenticationOptions> notification)
{
notification.HandleResponse();
string redirect = $"/Home/Error?message={notification.Exception.Message}";
if (notification.ProtocolMessage != null && !string.IsNullOrEmpty(notification.ProtocolMessage.ErrorDescription))
{
redirect += $"&debug={notification.ProtocolMessage.ErrorDescription}";
}
notification.Response.Redirect(redirect);
return Task.FromResult(0);
}
private async Task OnAuthorizationCodeReceivedAsync(AuthorizationCodeReceivedNotification notification)
{
var idClient = ConfidentialClientApplicationBuilder.Create(appId)
.WithRedirectUri(redirectUri)
.WithTenantId(tenantId)
.WithClientSecret(appSecret)
.WithAuthority(Authority)
.Build();
string email = string.Empty;
try
{
string[] scopes = graphScopes.Split(' ');
var result = await idClient.AcquireTokenByAuthorizationCode(
scopes, notification.Code).ExecuteAsync();
email = await GraphHelper.GetUserDetailsAsync(result.AccessToken);
}
catch (MsalException ex)
{
System.Diagnostics.Trace.TraceError(ex.Message);
}
notification.HandleResponse();
notification.Response.Redirect($"/Account/SignInAzure?email={email}");
}
private static string EnsureTrailingSlash(string value)
{
if (value == null)
{
value = string.Empty;
}
if (!value.EndsWith("/", StringComparison.Ordinal))
{
return value + "/";
}
return value;
}
}
}
My application is for single tenant so please don't suggest me to change the setting and make it for multi-tenant.
Please check below points:
After trying to change it to specific tenant i.e.;
After changing to Ex: - https://login.microsoftonline.com/contoso.onmicrosoft.com (or tenant id),
please save changes ,refresh portal / everything and try again.
If still it shows the error , check if the Application is registered to the Azure AD Tenant as Multi Tenant Application.
Then if it still remains check if the account is actually on Azure
AD ,as this error can occur when the user credentials you are trying
to use does not belong to the same tenant where the application is
actually registered in.
If it is different tenant and you are trying to access from different
account, then you may need to change its supported account types to
any organizational directory or you need to check for correct
credentials. If not check everything or create a new app registration
.
Also please check this "Use a tenant-specific endpoint or configure the application to be multi-tenant" when signing into my Azure website for possible
ways to solve the issue.
Else you can raise a support request
References:
msal - MsalException: Applicationis not configured as a multi-tenant
application. Android - Stack Overflow
Use single-tenant Azure AD apps with Microsoft Graph Toolkit -
Waldek Mastykarz

How does Azure AD MutiTenant authentication works?

I want to enable Multitenant Authentication. My Code is in ASP.Net Webforms and Here is the StartUp.cs file code.
public partial class Startup
{
const string MSATenantId = "XXXXXXXXXXXXXXX";
private static string clientId = ConfigurationManager.AppSettings["ida:ClientID"];
private static string aadInstance = EnsureTrailingSlash(ConfigurationManager.AppSettings["ida:AADInstance"]);
private static string authority = aadInstance + "common";
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions { });
// 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
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuers = new List<string>()
{
"https://sts/windows.net/XXXXXXXXXXXX"
}
// If the app needs access to the entire organization, then add the logic
// of validating the Issuer here.
// IssuerValidator
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
SecurityTokenValidated = (context) =>
{
//if (context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value != MSATenantId)
//{
// context.HandleResponse();
// context.Response.Redirect("InvalidUser.aspx");
//}
// If your authentication logic is based on users
return Task.FromResult(0);
},
AuthenticationFailed = (context) =>
{
// Pass in the context back to the app
context.HandleResponse();
// Suppress the exception
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);
}
//private Task OnSecurityTokenValidatedAsync(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
//{
// // Make sure that the user didn't sign in with a personal Microsoft account
// if (notification.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value == MSATenantId)
// {
// notification.HandleResponse();
// notification.Response.Redirect("/Account/UserMismatch");
// }
// return Task.FromResult(0);
//}
}
I want only the user with the MSATenantId should able to access the application for that I have read there are multiple ways I have tried below two though both are not working:
In this the application doesn't redirect to the Home page
TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuers = new List<string>()
{
"https://sts/windows.net/XXXXXXXXXX"
}
// If the app needs access to the entire organization, then add the logic
// of validating the Issuer here.
// IssuerValidator
},
In this it doesn't redirect to invalid page.
SecurityTokenValidated = (context) =>
{
if (context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value != MSATenantId)
{
context.HandleResponse();
context.Response.Redirect("InvalidUser.aspx");
}
If your authentication logic is based on users
return Task.FromResult(0);
},
Am I missing anything or do I need to add something in the above scenarios. I want to just test with one Tenant first and then I'll add more tenant.
Also, how does the 1 and 2 are different ?
This works if I don't use any of the above option. I am able to login with Azure account.
Your question has been resolved, add it as the answer to the end of the question.
Your issuer is set incorrectly, you should change it to: https://sts.windows.net/XXXXXXXXXXXX/.
I am able to resolve the issue. Issue was this url was incorrect https://sts/windows.net/XXXXXXXXXXXX
The correct URL is - https://sts.windows.net/XXXXXXXXXXXX/
TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuers = new List<string>()
{
"https://sts.windows.net/XXXXXXXXXX/"
}
// If the app needs access to the entire organization, then add the logic
// of validating the Issuer here.
// IssuerValidator
},

Azure AD Authentication redirects to home page

I'm building an Azure AD authentication which works fine, the only issue is that if a user clicks on a link that looks like host.com/xxx/bbb if they're not authenticated then they get redirected to root.
I need them to still be redirected to the original URL they entered in the browser. Can can this be achieved? Below is a snippet of the code I use in app startup:
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = XXX,
Authority = string.Format("https://login.microsoftonline.com/{0}", YYY,
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = async n => { await Task.Run(() => SetRedirectUrl(n)); }
}
});
}
private static void SetRedirectUrl(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
notification.ProtocolMessage.RedirectUri = notification.Request.Uri.AbsoluteUri;
}
None of the properties of OwinRequest contain a full path I'm looking for. I've also tried looking at
HttpContext.Current.Request.Url
but this also does not have the full address.
I need them to still be redirected to the original URL they entered in
the browser
You need to set the SignIn() method in AccountController.cs. You could use “Request.UrlReferrer.ToString();” to get the entered url:
public void SignIn()
{
// var host = Request.UserHostName.ToString();
var ori = Request.UrlReferrer.ToString();
var index = ori.LastIndexOf('/');
var action = ori.Substring(index);
// Send an OpenID Connect sign-in request.
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = action },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}

ASP.NET Core MVC 6 Azure B2C Active Directory Authentication issue

I followed the article : https://github.com/Azure-Samples/active-directory-dotnet-webapp-openidconnect-aspnetcore-b2c
In this sample app there is a Sign-in button. I am able to Sign-in successfully by clicking Sign-In button by providing my Azure B2C Tenant and registering the application in the tenant.
In another app, I want to authenticate without the Sign-In button being clicked i.e. right when I open the URL, I get redirected first to the Azure B2C AD login page, and after successful validation of credentials, I should be able to see the home screen.
So, what I did was from the URL mentioned from the article, I copied the SiginIn() method as:
public async Task<IActionResult> Index()
{
await SignIn();
await GetDataAsync();
}
I get an error message on running the application as : InvalidOperationException: No authentication handler is configured to handle the scheme: b2c_1_org_b2c_global_signin
Please advise how can I authenticate directly without the signin button. Previously with MVC5, I have successfully done this where I used [Authorize] attribute on the Controller class.
Controller Code with Index method
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using System.IO;
using System.Net;
using System.Text;
using Newtonsoft.Json;
using Microsoft.AspNetCore.Hosting;
using WebViewerCore.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Authorization;
namespace WebViewerCore.Controllers
{
[Authorize]
public class DocumentController : Controller
{
#region GlobalVariables
private static readonly string serviceUrl = "";
private string doctype = string.Empty;
private string dmsno = string.Empty;
public string documentName = string.Empty;
private string errMsg = string.Empty;
StringBuilder msg;
Document doc;
private IHostingEnvironment _env;
private IConfiguration _config;
#endregion
#region C'tor
public DocumentController(IHostingEnvironment env, IConfiguration config)
{
_env = env;
_config = config;
}
#endregion
#region ControllerAction
public async Task<IActionResult> Index()
{
//return View();
try
{
//await SignIn();
string storageAccount = _config.GetSection("BlobStorage").GetSection("StorageAccount").Value;
string storageContainer = _config.GetSection("BlobStorage").GetSection("StorageContainer").Value;
ViewBag.StorageAccount = storageAccount;
ViewBag.StorageContainer = storageContainer;
await GetDataAsync();
//HttpContext.Response.ContentType = "application/vnd.ms-xpsdocument";
if (TempData["QueryStringMissing"] != null && (bool)TempData["QueryStringMissing"] || doc == null)
{
return View("View");
}
else
{
return View("Index", doc);
}
}
catch (Exception ex)
{
//logger.LogErrorWithMessage(ex, ex.StackTrace);
//return View("Error", new HandleErrorInfo(ex, "Document", "Index"));
throw ex;
}
}
#endregion
Startup.cs code
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;
using Microsoft.AspNetCore.Http;
using System.IO;
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authentication.Cookies;
namespace WebViewerCore
{
public class Startup
{
#region Global Variables
public static string SignUpPolicyId;
public static string SignInPolicyId;
public static string ProfilePolicyId;
public static string ClientId;
public static string RedirectUri;
public static string AadInstance;
public static string Tenant;
#endregion
public Startup(IHostingEnvironment env)
{
//var builder = new ConfigurationBuilder()
// .SetBasePath(env.ContentRootPath)
// .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
// .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
// .AddEnvironmentVariables();
//Configuration = builder.Build();
Configuration = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables()
.Build();
}
public IConfigurationRoot Configuration { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
// Adds a default in-memory implementation of IDistributedCache.
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.CookieHttpOnly = true;
});
services.AddSingleton<IConfiguration>(Configuration);
// Add Authentication services.
services.AddAuthentication(sharedOptions => sharedOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseSession();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Document}/{action=Index}/{id?}");
});
// App config settings
ClientId = Configuration["AzureAD:ClientId"];
AadInstance = Configuration["AzureAD:AadInstance"];
Tenant = Configuration["AzureAD:Tenant"];
RedirectUri = Configuration["AzureAD:RedirectUri"];
// B2C policy identifiers
SignUpPolicyId = Configuration["AzureAD:SignUpPolicyId"];
SignInPolicyId = Configuration["AzureAD:SignInPolicyId"];
// Configure the OWIN pipeline to use OpenID Connect auth.
//app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(SignUpPolicyId));
app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(SignInPolicyId));
}
private OpenIdConnectOptions CreateOptionsFromPolicy(string policy)
{
policy = policy.ToLower();
return new OpenIdConnectOptions
{
// For each policy, give OWIN the policy-specific metadata address, and
// set the authentication type to the id of the policy
MetadataAddress = string.Format(AadInstance, Tenant, policy),
AuthenticationScheme = policy,
CallbackPath = new PathString(string.Format("/{0}", policy)),
// These are standard OpenID Connect parameters, with values pulled from config.json
ClientId = ClientId,
PostLogoutRedirectUri = RedirectUri,
Events = new OpenIdConnectEvents
{
OnRemoteFailure = RemoteFailure,
},
ResponseType = OpenIdConnectResponseType.IdToken,
// This piece is optional - it is used for displaying the user's name in the navigation bar.
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
}
};
}
// Used for avoiding yellow-screen-of-death
private Task RemoteFailure(FailureContext context)
{
context.HandleResponse();
if (context.Failure is OpenIdConnectProtocolException && context.Failure.Message.Contains("access_denied"))
{
context.Response.Redirect("/");
}
else
{
context.Response.Redirect("/Home/Error?message=" + context.Failure.Message);
}
return Task.FromResult(0);
}
}
}
To automatically redirect users navigating to a specific controller or endpoint in MVC, all you need to do is add the [Authorize] attribute, provided you've configured your middleware correctly.
In the case of Azure AD B2C, you need to make sure you add the OpenID Middleware like so:
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
// For each policy, give OWIN the policy-specific metadata address, and
// set the authentication type to the id of the policy
MetadataAddress = string.Format(AadInstance, Tenant, policy),
AuthenticationScheme = policy,
CallbackPath = new PathString(string.Format("/{0}", policy)),
// These are standard OpenID Connect parameters, with values pulled from config.json
ClientId = ClientId,
PostLogoutRedirectUri = RedirectUri,
Events = new OpenIdConnectEvents
{
OnRemoteFailure = RemoteFailure,
},
ResponseType = OpenIdConnectResponseType.IdToken,
// This piece is optional - it is used for displaying the user's name in the navigation bar.
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
},
};);
The sample you referenced already does what you want, to a point.
In that sample, if you haven't signed in and navigate to /about, you'll get automatically redirected to Azure AD B2C to sign in.
As it stands, the sample has the [Authorize] attribute only on a controller action in the Home controller, you'll want to move that up to the controller level and add it to every controller. Then you can just remove the the sign-in/sign-up/sign-out buttons and controller action.

Azure active directory and owin authentication

Just faced an strange issue with azure ad applicationS and owin openid authentication.
To reproduce the issue.
1.create a web app with azure ad authentication in vs 2015 choosing cloud app template .
2.let the standard code be as is.
3.let startup.auth as is.
4.Run the app on local it works fine.
5.now change code in startup àuth as follows
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";
private static readonly log4net.ILog logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public void ConfigureAuth(IAppBuilder app)
{
ApplicationDbContext db = new ApplicationDbContext();
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
logger.Debug("SetDefaultSignInAsAuthenticationType called");
//app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseCookieAuthentication(
new CookieAuthenticationOptions
{
Provider = new CookieAuthenticationProvider
{
OnResponseSignIn = ctx =>
{
//logger.Debug("OnResponseSignIn called");
////ctx.Identity = TransformClaims(ctx.Identity);
//logger.Debug("TransformClaims called");
}
}
});
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;
logger.Debug("OnResponseSignIn called");
logger.Debug("signedInUserID =" + signedInUserID);
TransformClaims(context.AuthenticationTicket.Identity);
logger.Debug("TransformClaims called");
AuthenticationContext authContext = new AuthenticationContext(Authority, new ADALTokenCache(signedInUserID));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);
return Task.FromResult(0);
},
// we use this notification for injecting our custom logic
SecurityTokenValidated = (context) =>
{
logger.Debug("SecurityTokenReceived called");
//TransformClaims(); //pass the identity
return Task.FromResult(0);
},
}
});
}
private static void TransformClaims(System.Security.Claims.ClaimsIdentity identity)
{
if (identity != null && identity.IsAuthenticated == true)
{
var usserobjectid = identity.FindFirst(ConfigHelpers.Azure_ObjectIdClaimType).Value;
((System.Security.Claims.ClaimsIdentity)identity).AddClaim(new System.Security.Claims.Claim("DBID", "999"));
((System.Security.Claims.ClaimsIdentity)identity).AddClaim(new System.Security.Claims.Claim("Super","True"));
}
// return identity;
}
}
6.Run the app on local it will work perfect.
7.Deploy the app on azure websites and the startup àuth owin notification methods will never be called.however app works but identity transformation not
Can somebody help out what's wrong with this is azure ad apps doesn't support cookies or notification not firing or anything wrong with code.
Just to re-assert else than startup.àuth no standard code is changed.
I know this is a bit old, but I had exactly the same issue recently and spent hours trying to understand why it wouldn't work in Azure but it worked absolutely fine in my localhost.
This is basically a configuration issue: in portal.azure.com select your app and then go to settings > authentication / authorisation and make sure that app service authentication is OFF.
It turns out that this setting will take over your startup.auth settings.
I have to give full credit to Vittorio Bertocci as pointed that out to me.

Resources