Enabling Azure AD auth on one page of a web forms application - azure

I am the dev lead on a web forms project. There are 2 "modes" to the login page, one for customers, and one for admins.
For additional security, we would like to enforce admin users to authenticate with their Azure AD creds.
Is there a way at the application level for a specific page/URL to kick off the Azure AD auth based on a specific URL? We cannot do this from Azure itself as obviously our customers are not in our AD account.

Just mark admin view controllers with [Authorize] attribute and configure Openid authentication from within the app. This means all views served from this controlled class will require user authentication.
There are a lot of examples on the web how to do this. In case of .net core 2.0 here is the sample.
AccountController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Mvc;
namespace WebApp_OpenIDConnect_DotNet.Controllers
{
[Route("[controller]/[action]")]
public class AccountController : Controller
{
[HttpGet]
public IActionResult SignIn()
{
var redirectUrl = Url.Action(nameof(HomeController.Index), "Home");
return Challenge(
new AuthenticationProperties { RedirectUri = redirectUrl },
OpenIdConnectDefaults.AuthenticationScheme);
}
[HttpGet]
public IActionResult SignOut()
{
var callbackUrl = Url.Action(nameof(SignedOut), "Account", values: null, protocol: Request.Scheme);
return SignOut(
new AuthenticationProperties { RedirectUri = callbackUrl },
CookieAuthenticationDefaults.AuthenticationScheme,
OpenIdConnectDefaults.AuthenticationScheme);
}
[HttpGet]
public IActionResult SignedOut()
{
if (User.Identity.IsAuthenticated)
{
// Redirect to home page if the user is authenticated.
return RedirectToAction(nameof(HomeController.Index), "Home");
}
return View();
}
[HttpGet]
public IActionResult AccessDenied()
{
return View();
}
}
}
And in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAd(options => Configuration.Bind("AzureAd", options))
.AddCookie();
services.AddMvc();
}
and in Configure Method add
app.UseAuthentication();
And lastly in appsettings.json
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "[Enter the domain of your tenant, e.g. contoso.onmicrosoft.com]",
"TenantId": "[Enter the Tenant Id (Obtained from the Azure portal. Select 'Endpoints' from the 'App registrations' blade and use the GUID in any of the URLs), e.g. da41245a5-11b3-996c-00a8-4d99re19f292]",
"ClientId": "[Enter the Client Id (Application ID obtained from the Azure portal), e.g. ba74781c2-53c2-442a-97c2-3d60re42f403]",
"CallbackPath": "/signin-oidc"
}
}
More here
https://azure.microsoft.com/en-us/resources/samples/active-directory-dotnet-webapp-openidconnect-aspnetcore/

Related

Implement Active Directory Group in Asp.Net CORE 3.x

Asp.net CORE 3.x :
The authentication is working fine with Azure Active Directory.
Now, i would like to implement the authorization a specific AD Group for all routes.
How to implement this authorization ? steps by steps with Asp.NET Core ?
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = AzureADDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = AzureADDefaults.AuthenticationScheme;
}).AddAzureAD(options => Configuration.Bind("AzureAD", options));
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseHttpsRedirection();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute().RequireAuthorization();
//endpoints.MapControllers();
});
}
}
}
Thank you for you help ! :)
You can use groups claims in Azure AD , config the your application in azure portal to receive group claims by editing the manifest :
{
...
"errorUrl": null,
"groupMembershipClaims": "SecurityGroup",
...
}
ID token issued from Azure AD will include the current user's groups id list in groups claim , then in asp.net core application , you can restrict the access by :
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser().RequireClaim("groups", "YourGroupID")
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
Note : From document :
If a user is member of more groups than the overage limit (150 for SAML tokens, 200 for JWT tokens), then the Microsoft Identity Platform does not emit the groups claim in the token. Instead, it includes an overage claim in the token that indicates to the application to query the Graph API to retrieve the user’s group membership.

Activate Azure Ad authentication when you hit https://host:port/swagger on net Core 2 Api?

I make all changes on my api to use Azure Ad with this and this link features, but when the api is deployed, I need to make the user who gets the Url https://myapi.com/swagger (for example) to redirect it to azure Ad login,then know if the client have rights or not to use this api and redirect it again to my api and show the enpoints he have access.
I make some changes on startup.cs to use OpenIdConnect
//Add AddAzureAdBearer Auth options
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = OpenIdConnectDefaults.AuthenticationScheme;
//options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddOpenIdConnect(option =>
{
option.ClientId = Client;
option.Authority = $"{Instance}/{Tenant}";
option.SignedOutRedirectUri = "https://localhost:44308";
option.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
option.SaveTokens = true;
option.Events = new OpenIdConnectEvents
{
OnRemoteFailure = context =>
{
context.HandleResponse();
return Task.CompletedTask;
}
};
})
.AddCookie()
.AddAzureAdBearer(options => _configuration.Bind("Ad", options));
And I add a HomeController to redirect to swagger UI:
[Authorize]
public class HomeController : Controller
{
[HttpGet("")]
public ActionResult Index()
{
return Redirect("~/swagger");
}
}
When I launch the api, it works as spected, but when y write https://{host:port}/swagger it does not work, don't hit the authentication process and goes to https://{host:port}/swagger/index.html automatically.
How can I fix this?
I'm working with net core 2.0 and Swashbuckle for swagger.
You you need to add Swagger support to ConfigureServices(IServiceCollection services) and to Configure(IApplicationBuilder app, IHostingEnvironment env) in your application’s Startup.cs file. To do so, you need to create a SwaggerServiceExtensions class and add the necessary code to support Swagger in your app.
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Swashbuckle.AspNetCore.Swagger;
namespace JwtSwaggerDemo.Infrastructure
{
public static class SwaggerServiceExtensions
{
public static IServiceCollection AddSwaggerDocumentation(this IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1.0", new Info { Title = "Main API v1.0", Version = "v1.0" });
c.AddSecurityDefinition("Bearer", new ApiKeyScheme
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
Name = "Authorization",
In = "header",
Type = "apiKey"
});
});
return services;
}
public static IApplicationBuilder UseSwaggerDocumentation(this IApplicationBuilder app)
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1.0/swagger.json", "Versioned API v1.0");
c.DocExpansion("none");
});
return app;
}
}
}
Changes in Startup.cs file
Using the above class, the only thing you need to do in your Startup.cs file is the following:
namespace JwtSwaggerDemo
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
//... rest of services configuration
services.AddSwaggerDocumentation();
//...
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
//.... rest of app configuration
app.UseSwaggerDocumentation();
}
//.... rest of app configuration
}
}
}
Authorize requests in Swagger UI
Now, when you load the Swagger’s UI address (e.g: https://localhost:44321/swagger/#/), you will see an Authorize button at the top. Clicking on it leads to a modal window, which allows you to authorize your app with a JWT token, by adding Bearer in the value input field.

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 B2C Migration from Preview to Production tenant Leads to successful login without the Iprincipal being passed back

Guys I am not including code ATM because this seems to be a Azure B2C setup question not a coding question. If needed I will upload it but the code in question is quite a bit to read thru.
Q: Has anyone successfully migrated from a Azure B2C "Preview" Tenant to a "Production" Tenant?
We had a functioning website using the B2C "Preview" Tenant and Microsoft informed us that we needed to create a "Production" Tenant now that it had been released. We deleted the "Preview" tenant and created back the "Production" tenant with the same name but when we did that we lost our fallback to the working "Preview" Tenant. The New "Production" tenant failed to create with the b2c_extensions-app that we had in the "Preview" Tenant which we believe caused it to be not functional. So we created a second "Production" Tenant with a new name and that did create with the b2c-extensions-apps and we proceeded to change the web apps settings to point to the new name. Now when we signup the user gets created in the new B2C AD but when Microsoft comes back to our return URL the returned IPrincipal has no claims and the User.Identity.IsAuthenticated is false. How can a user get created in the B2C and return you get a User.Identity.IsAuthenticated=false?
Additional Info: the ID_Token is on the authresp. It is looking like MVC is not decrypting the encrypted token and creating the Iprincipal User. We are currently using package System.IdentityModel.Tokens.Jwt version 4.0.2.206221351. Could it be that the new Production version of B2C AD only works with System.IdentityModel.Tokens.Jwt version 5.0.0?
Well it took a month of Microsoft working on it to get a workaround to this and get our website back up and working. Bottom line: Below is the takeaways from this experience.
1) Do not delete your Preview B2C until you are sure your Production one is 100% working with your website.
2) When creating your production B2C do not use the same name as you did on your Preview B2C. ( this is a known bug to Microsoft. Good to know after the fact, Right?)
3) Do not use the same names for your Sign-in, Sign-up, Password-Reset, or Profile-editing policies. ( this is critical.)
You must change the port number you use for testing locally in the Azure B2C Application Reply Url, your web.config ReturnURL variable, and in the projects properties setting. (This was also critical for us.)
5). Microsoft had us change the calls to OpenIdConnect in the following places:
a) the ida:AadInstance in the web.config to
<add key="ida:AadInstance" value="https://login.microsoftonline.com/{0}/v2.0/.well-known/openid-configuration?p={1}"/>
b) The modified code in App_Start/Startup.Auth.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using System.Threading.Tasks;
using Microsoft.Owin.Security.Notifications;
using Microsoft.IdentityModel.Protocols;
using System.Web.Mvc;
using System.Configuration;
using System.IdentityModel.Tokens;
using System.Threading;
using System.Globalization;
using Microsoft.Owin;
namespace WebSite
{
public partial class Startup
{
// The ACR claim is used to indicate which policy was executed
public const string AcrClaimType = "http://schemas.microsoft.com/claims/authnclassreference";
// App config settings
public static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
public static string aadInstance = ConfigurationManager.AppSettings["ida:AadInstance"];
public static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
public static string redirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
// B2C policy identifiers
public static string SignUpPolicyId = ConfigurationManager.AppSettings["ida:SignUpPolicyId"];
public static string SignInPolicyId = ConfigurationManager.AppSettings["ida:SignInPolicyId"];
public static string ProfilePolicyId = ConfigurationManager.AppSettings["ida:UserProfilePolicyId"];
public static string ChangePasswordPolicyId = ConfigurationManager.AppSettings["ida:ChangePasswordPolicyId"];
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
// Configure OpenID Connect middleware for each policy
app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(SignUpPolicyId));
app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(SignInPolicyId));
app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(ProfilePolicyId));
app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(ChangePasswordPolicyId));
}
// Used for avoiding yellow-screen-of-death
private Task AuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
notification.HandleResponse();
if (notification.Exception.Message == "access_denied")
{
notification.Response.Redirect("/");
}
else
{
notification.Response.Redirect("/Home/Error?message=" + notification.Exception.Message);
}
return Task.FromResult(0);
}
private OpenIdConnectAuthenticationOptions CreateOptionsFromPolicy(string policy)
{
return new OpenIdConnectAuthenticationOptions
{
MetadataAddress = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant, policy),
AuthenticationType = policy,
ClientId = clientId,
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = AuthenticationFailed
},
Scope = "openid",
ResponseType = "id_token",
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
SaveSigninToken = true //important to save the token in boostrapcontext
},
ProtocolValidator = new OpenIdConnectProtocolValidator { RequireNonce = false }
};
}
}
}
c) The modified code in Controllers/AccountController.cs (see attached code)
using Microsoft.Owin.Security;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Microsoft.Owin.Security.OpenIdConnect;
using Microsoft.Owin.Security.Cookies;
using System.Security.Claims;
using EMC_Portal_Web.Services.DataAccess;
using EMC_Portal_Web;
namespace WebSite.Controllers
{
public class AccountController : Controller
{
public void SignIn()
{
// To execute a policy, you simply need to trigger an OWIN challenge.
// You can indicate which policy to use by adding it to the AuthenticationProperties using the PolicyKey provided.
try
{
if (!Request.IsAuthenticated)
{
// To execute a policy, you simply need to trigger an OWIN challenge.
// You can indicate which policy to use by specifying the policy id as the AuthenticationType
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = Startup.redirectUri }, Startup.SignInPolicyId);
}
}
catch (Exception ex)
{
Trace.TraceError("Error Message: " + ex.Message + " Stack: " + ex.StackTrace);
}
}
public void SignUp()
{
try
{
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = Startup.redirectUri }, Startup.SignUpPolicyId);
}
}
catch (Exception ex)
{
Trace.TraceError("Error Message: " + ex.Message + " Stack: " + ex.StackTrace);
}
}
public new void Profile()
{
try
{
if (Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = Startup.redirectUri }, Startup.ProfilePolicyId);
}
}
catch (Exception ex)
{
Trace.TraceError("Error Message: " + ex.Message + " Stack: " + ex.StackTrace);
}
}
public void ChangePassword()
{
try
{
if (Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = Startup.redirectUri }, Startup.ChangePasswordPolicyId);
}
}
catch (Exception ex)
{
Trace.TraceError("Error Message: " + ex.Message + " Stack: " + ex.StackTrace);
}
}
public ActionResult SignOut()
{
try
{
if (Request.IsAuthenticated)
{
IEnumerable<AuthenticationDescription> authTypes = HttpContext.GetOwinContext().Authentication.GetAuthenticationTypes();
HttpContext.GetOwinContext().Authentication.SignOut(authTypes.Select(t => t.AuthenticationType).ToArray());
}
return Redirect(System.Web.HttpContext.Current.Application["Index"].ToString());
}
catch (Exception ex)
{
Trace.TraceError("Error Message: " + ex.Message + " Stack: " + ex.StackTrace);
return Redirect(System.Web.HttpContext.Current.Application["Home"].ToString());
}
}
}
}

Azure App Service Mobile authentication not compatible with MVC/Forms

I have added a Web API service to a 'legacy' MVC project (not vNext), still using Membership and the forms authentication module.
All OK, until I decided to make the web api a mobile service using the latest SDKs for Azure App Services for Mobile.
I have so far narrowed the problem to this
//app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions()
//{
// SigningKey = CloudConfigurationManager.GetSetting("authSigningKey"),
// ValidAudiences = new[] { CloudConfigurationManager.GetSetting("authAudience") },
// ValidIssuers = new[] { CloudConfigurationManager.GetSetting("authIssuer") },
// TokenHandler = GlobalConfiguration.Configuration.GetAppServiceTokenHandler()
//});
This changes the rest of the app so that MVC + forms auth doesn't work. Running out of time to research the problem.
Any clues??
The following solution is working for me:
In your Startup register call UseCookieAuthentication, UseExternalSignInCookie or UseOAuthAuthorizationServer before calling UseAppServiceAuthentication.
Second step:
Add the following class to your project:
private sealed class CustomAppServiceAuthenticationMiddleware : AppServiceAuthenticationMiddleware
{
private readonly ILogger _logger;
public CustomAppServiceAuthenticationMiddleware(OwinMiddleware next, IAppBuilder appBuilder, AppServiceAuthenticationOptions options) : base(next, appBuilder, options)
{
_logger = (ILogger)GetType().BaseType.GetField("logger", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(this);
}
protected override AuthenticationHandler<AppServiceAuthenticationOptions> CreateHandler()
{
return new AppServiceAuthenticationHandler(_logger);
}
public override Task Invoke(IOwinContext context)
{
string logLine = $"AppServiceAuthMiddleware: {context.Request.Path}";
if (context.Request.Headers.TryGetValue("Authorization", out var values))
logLine += $"; Authorization: {values.First().Split(' ').FirstOrDefault()}";
if (context.Request.Headers.TryGetValue("X-ZUMO-AUTH", out values))
logLine += $"; X-ZUMO-AUTH: {values.First()}";
_logger.WriteVerbose(logLine);
Debug.WriteLine(logLine);
if (IsZumoAuth(context))
{
return base.Invoke(context);
}
return Next.Invoke(context);
}
private bool IsZumoAuth(IOwinContext context)
{
return context.Request.Headers.ContainsKey("X-ZUMO-AUTH");
}
}
Thrid step:
Replace the app.UseAppServiceAuthentication with the following:
app.Use(typeof(CustomAppServiceAuthenticationMiddleware), app, new AppServiceAuthenticationOptions
{
SigningKey = ...,
ValidAudiences = ...,
ValidIssuers = ...,
TokenHandler = ...
});
This will the owin pipline make call the AppServiceAuthenticationMiddleware just for ZUMO-AUTH auth.
I've got a mixed web & mobile app.
With this approach the membership auth on the web app is working.
In the app, some custom oauth (refresh token based) plus the azure auth (facebook, google, ...) is working too. All that within the same asp.net application.

Resources