ASP.NET MVC 5 - ADAL to MSAL 2.0 migration - asp.net-mvc-5

I've tried to follow this sample on link to implement MSAL authentication (authorization code flow) to our app running in .NET 4.8 platform:
https://github.com/Azure-Samples/ms-identity-aspnet-webapp-openidconnect/blob/master/WebApp
I implement the MSAL code in the following file of our app
Startup.cs
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using CompanyApp.Infrastructure;
using CompanyApp.App_Start;
using Owin;
using Microsoft.Owin;
using System.Web.Http;
using System.Net.Http.Formatting;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using Microsoft.Owin.Security.Notifications;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using System.Web;
using Microsoft.Identity.Web;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Owin.Host.SystemWeb;
using CompanyApp.Utils;
namespace CompanyApp
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = AuthenticationConfig.ClientId,
Authority = AuthenticationConfig.Authority,
RedirectUri = AuthenticationConfig.RedirectUri,
PostLogoutRedirectUri = AuthenticationConfig.RedirectUri,
Scope = AuthenticationConfig.BasicSignInScopes + $" User.Read",
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = false
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed
}
}
);
RegisterConstants(app);
RegisterAppFilters(AppFilters.Filters);
HttpConfiguration config = new HttpConfiguration() {
};
config.Formatters.Clear();
config.Formatters.Add(new JsonMediaTypeFormatter());
// config.EnsureInitialized();
app.UseWebApi(config);
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
// Upon successful sign in, get the access token & cache it using MSAL
IConfidentialClientApplication clientApp = MsalAppBuilder.BuildConfidentialClientApplication();
AuthenticationResult result = await clientApp.AcquireTokenByAuthorizationCode(new[] { "api://<Application ID in azure>/.default" }, context.Code).ExecuteAsync();
}
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
notification.HandleResponse();
notification.Response.Redirect("/Error?message=" + notification.Exception.Message);
return Task.FromResult(0);
}
}
}
HomeController.cs
using Microsoft.Identity.Client;
using Microsoft.Identity.Web;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.OpenIdConnect;
using System;
using System.Diagnostics;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using CompanyApp.Utils;
namespace CompanyApp.Controllers
{
public class HomeController : Controller
{
[Authorize]
public ActionResult Index()
{
IConfidentialClientApplication app = MsalAppBuilder.BuildConfidentialClientApplication();
var msalAccountId = ClaimsPrincipal.Current.GetMsalAccountId(); // getting null from this line
var account = await app.GetAccountAsync(msalAccountId);
string[] scopes = { "api://<Application ID in azure>/.default" };
try
{
// try to get an already cached token
await app.AcquireTokenSilent(scopes, account).ExecuteAsync().ConfigureAwait(false);
}
catch (MsalUiRequiredException ex)
{
throw ex;
}
return View();
}
}
}
I tried to run this in my local
after it successfully authenticated and goes to the controller
I am getting null result from the line where ClaimsPrincipal.Current.GetMsalAccountId() is invoked
Is there something that is missing for ClaimsPrincipal.Current.GetMsalAccountId() to give out null?

In ASP.NET 4.x projects, it was common to use ClaimsPrincipal.Current to retrieve the current authenticated user's identity and claims. In ASP.NET Core, this property is no longer set. Code that was depending on it needs to be updated to get the current authenticated user's identity through a different means.
There are several options for retrieving the current authenticated user's ClaimsPrincipal in ASP.NET Core in place of ClaimsPrincipal.Current:
1)ControllerBase.User. MVC controllers can access the current authenticated user with their User property.
2)HttpContext.User. Components with access to the current HttpContext (middleware, for example) can get the current user's ClaimsPrincipal from HttpContext.User.
3)Passed in from caller. Libraries without access to the current HttpContext are often called from controllers or middleware components and can have the current user's identity passed as an argument.
4)HttpContextAccessor. The project being migrated to ASP.NET Core may be too large to easily pass the current user's identity to all necessary locations. In such cases, IHttpContextAccessor can be used as a workaround. IHttpContextAccessor is able to access the current HttpContext (if one exists). If DI is being used, see Access HttpContext in ASP.NET Core. A short-term solution to getting the current user's identity in code that hasn't yet been updated to work with ASP.NET Core's DI-driven architecture would be:
For more details refer this document

Related

Azure Function create and read JWT without using Active Directory

I'm trying to create and read (validate) a JSON Web Token (JWT) in an Azure Function using C#. I came across this post:
https://www.codeproject.com/Tips/1208535/Create-And-Consume-JWT-Tokens-in-csharp
which outlines the process very nicely. Being relatively new to Azure Functions, I put the reference to "System.IdentityModel.Tokens.Jwt" in my project.json file like this:
{
"frameworks": {
"net46":{
"dependencies": {
"System.IdentityModel.Tokens.Jwt" : "5.0"
}
}
}
}
The version I used came from this post: Namespaces for .NET JWT token validation: System vs. Microsoft, which talks about versioning issues back in 2016.
Unfortunately, this didn't work. References to SecurityAlgorithms, JwtHeader, JwtPayload, JwtSecurityToken, and JwtSecurityTokenHandler all report, "[run.csx] The type or namespace name 'class name' could not be found (are you missing a using directive or an assembly reference?)".
Researching further, I discovered this page: https://www.nuget.org/packages/System.IdentityModel.Tokens.Jwt/, which displays Nuget version information for System.IdentityModel.Tokens.Jwt. After trying several versions (by changing the version in my project.json file), I've still had no luck in getting the Function App to recognize the classes I need.
I assume that this is a versioning issue. If so, where can I go to determine which version of "System.IdentityModel.Tokens.Jwt" is compatible with "net46"? I haven't written C# code in years (I'm a Java developer), so I may be wrong about the versioning assumption.
BTW, here's what the code in my function looks like, it's appears exactly like the code sample in: https://www.codeproject.com/Tips/1208535/Create-And-Consume-JWT-Tokens-in-csharp. The only difference is I've wrapped it in a Function App.
using System.Net;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using System.IdentityModel;
using System.Security;
using System.Text;
using System.IdentityModel.Tokens;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
// Define const Key this should be private secret key stored in some safe place
string key = "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429090fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1";
// Create Security key using private key above:
// not that latest version of JWT using Microsoft namespace instead of System
var securityKey = new Microsoft
.IdentityModel.Tokens.SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
// Also note that securityKey length should be >256b
// so you have to make sure that your private key has a proper length
//
var credentials = new Microsoft.IdentityModel.Tokens.SigningCredentials
(securityKey, SecurityAlgorithms.HmacSha256Signature);
// Finally create a Token
var header = new JwtHeader(credentials);
//Some PayLoad that contain information about the customer
var payload = new JwtPayload
{
{ "some ", "hello "},
{ "scope", "http://dummy.com/"},
};
//
var secToken = new JwtSecurityToken(header, payload);
var handler = new JwtSecurityTokenHandler();
// Token to String so you can use it in your client
var tokenString = handler.WriteToken(secToken);
// And finally when you received token from client
// you can either validate it or try to read
var token = handler.ReadJwtToken(tokenString);
return req.CreateResponse(HttpStatusCode.Created, "test");
}
So, my questions are:
Which version of System.IdentityModel.Tokens should I use with "net46" in my project file?
The next time this happens, how do I determine myself which versions work together?
I just tried this and saw the same thing. You're missing a reference to System.IdentityModel and a using System.IdentityModel.Tokens.Jwt;
Changing to this got things building:
#r "System.IdentityModel"
using System.Net;
using System.IdentityModel;
using System.Security;
using System.Text;
using System.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
I'd also recommend you move your JWT package reference up to 5.2.4, which is the latest version of that package.
I figured it out. From various sites and hundreds of combinations of versions, it works.
I wish I could explain why, but instead I'll post the working code here with the appropriate libraries listed. If anyone else runs into this problem, I hope it helps. Thanks for looking into this brettsam!
The Function App looks like this:
using System;
using System.Net;
using System.Text;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
using System.Configuration;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
string token = JwtManager.GenerateToken("rbivens#mydomain.com", 60);
ClaimsPrincipal simplePrinciple = JwtManager.GetPrincipal(token);
var identity = simplePrinciple.Identity as ClaimsIdentity;
log.Info(identity.IsAuthenticated.ToString());
var usernameClaim = identity.FindFirst(ClaimTypes.Name);
var username = usernameClaim ? .Value;
log.Info(username);
return req.CreateResponse(HttpStatusCode.Created, token);
}
public static class JwtManager
{
private static string secret = ConfigurationManager.AppSettings["FunctionsJwtSecret"];
public static string GenerateToken(string username, int expireMinutes = 60)
{
var symmetricKey = Convert.FromBase64String(secret);
var tokenHandler = new JwtSecurityTokenHandler();
var now = DateTime.UtcNow;
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, username)
}),
Expires = now.AddMinutes(Convert.ToInt32(expireMinutes)),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(symmetricKey), SecurityAlgorithms.HmacSha256Signature)
};
var stoken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(stoken);
return token;
}
public static ClaimsPrincipal GetPrincipal(string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var jwtToken = tokenHandler.ReadToken(token) as JwtSecurityToken;
if (jwtToken == null)
return null;
var symmetricKey = Convert.FromBase64String(secret);
var validationParameters = new TokenValidationParameters()
{
RequireExpirationTime = true,
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(symmetricKey)
};
SecurityToken securityToken;
var principal = tokenHandler.ValidateToken(token, validationParameters, out securityToken);
// log.Info(securityToken.ToString());
return principal;
}
catch (Exception)
{
return null;
}
}
}
And the project.json looks like this:
{
"frameworks": {
"net46":{
"dependencies": {
"Microsoft.IdentityModel.Logging" : "1.0.0.127",
"Microsoft.IdentityModel.Tokens" : "5.0.0.127",
"Newtonsoft.Json" : "9.0.0.0",
"System.IdentityModel.Tokens.Jwt" : "5.0.0.127"
}
}
}
}
Again, I don't know why this combination of versions work together, but I hope this saves someone else 20 hours of tedious trial and error.

ServiceStack .Net Core fluent validation Not consistent with full .NET 4.6.2

So we have a working ServiceStack service hosted inside a Windows Service using .Net 4.6.2, which uses a bunch of Fluent Validation validators.
We would like to port this to .Net Core. So I started to create cut down project just with a few of the features of our main app to see what the port to .Net Core would be like.
Most things are fine, such as
IOC
Routing
Hosting
Endpoint is working
The thing that does not seem to be correct is validation. To illustrate this I will walk through some existing .Net 4.6.2 code and then the .Net Core code. Where I have included the results for both
.NET 4.6.2 example
This is all good when using the full .Net 4.6.2 framework and the various ServiceStack Nuget packages.
For example I have this basic Dto (please ignore the strange name, long story not my choice)
using ServiceStack;
namespace .RiskStore.ApiModel.Analysis
{
[Route("/analysis/run", "POST")]
public class AnalysisRunRequest : BaseRequest, IReturn<AnalysisRunResponse>
{
public AnalysisPart Analysis { get; set; }
}
}
Where we have this base class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace .RiskStore.ApiModel
{
public abstract class BaseRequest
{
public string CreatedBy { get; set;}
}
}
And we have this validator (we have way more than this working in .Net 4.6.2 app, this is just to show differences between full .Net and .Net Core which we will see in a minute)
using .RiskStore.ApiModel.Analysis;
using ServiceStack.FluentValidation;
namespace .RiskStore.ApiServer.Validators.Analysis
{
public class AnalysisRunRequestValidator : AbstractValidator<AnalysisRunRequest>
{
public AnalysisRunRequestValidator(AnalysisPartValidator analysisPartValidator)
{
RuleFor(analysis => analysis.CreatedBy)
.Must(HaveGoodCreatedBy)
.WithMessage("CreatedBy MUST be 'sbarber'")
.WithErrorCode(ErrorCodes.ValidationErrorCode);
}
private bool HaveGoodCreatedBy(AnalysisRunRequest analysisRunRequest, string createdBy)
{
return createdBy == "sbarber";
}
}
}
And here is my host file for this service
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Funq;
using .RiskStore.ApiModel.Analysis;
using .RiskStore.ApiModel.Analysis.Results;
using .RiskStore.ApiServer.Api.Analysis;
using .RiskStore.ApiServer.Exceptions;
using .RiskStore.ApiServer.IOC;
using .RiskStore.ApiServer.Services;
using .RiskStore.ApiServer.Services.Results;
using .RiskStore.ApiServer.Validators.Analysis;
using .RiskStore.ApiServer.Validators.Analysis.Results;
using .RiskStore.DataAccess.AnalysisRun.Repositories.Results;
using .RiskStore.DataAccess.AnalysisRun.Repositories.Search;
using .RiskStore.DataAccess.AnalysisRun.Repositories.Validation;
using .RiskStore.DataAccess.Configuration;
using .RiskStore.DataAccess.Connectivity;
using .RiskStore.DataAccess.Ingestion.Repositories.EventSet;
using .RiskStore.DataAccess.JobLog.Repositories;
using .RiskStore.DataAccess.StaticData.Repositories;
using .RiskStore.DataAccess.UnitOfWork;
using .RiskStore.Toolkit.Configuration;
using .RiskStore.Toolkit.Jobs.Repositories;
using .RiskStore.Toolkit.Storage;
using .RiskStore.Toolkit.Utils;
using .Toolkit;
using .Toolkit.Configuration;
using .Toolkit.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using ServiceStack;
using ServiceStack.Text;
using ServiceStack.Validation;
namespace .RiskStore.ApiServer.Api
{
public class ApiServerHttpHost : AppHostHttpListenerBase
{
private readonly ILogger _log = Log.ForContext<ApiServerHttpHost>();
public static string RoutePrefix => "analysisapi";
/// <summary>
/// Base constructor requires a Name and Assembly where web service implementation is located
/// </summary>
public ApiServerHttpHost()
: base(typeof(ApiServerHttpHost).FullName, typeof(AnalysisServiceStackService).Assembly)
{
_log.Debug("ApiServerHttpHost constructed");
}
public override void SetConfig(HostConfig config)
{
base.SetConfig(config);
JsConfig.TreatEnumAsInteger = true;
JsConfig.EmitCamelCaseNames = true;
JsConfig.IncludeNullValues = true;
JsConfig.AlwaysUseUtc = true;
JsConfig<Guid>.SerializeFn = guid => guid.ToString();
JsConfig<Guid>.DeSerializeFn = Guid.Parse;
config.HandlerFactoryPath = RoutePrefix;
var exceptionMappings = new Dictionary<Type, int>
{
{typeof(JobServiceException), 400},
{typeof(NullReferenceException), 400},
};
config.MapExceptionToStatusCode = exceptionMappings;
_log.Debug("ApiServerHttpHost SetConfig ok");
}
/// <summary>
/// Application specific configuration
/// This method should initialize any IoC resources utilized by your web service classes.
/// </summary>
public override void Configure(Container container)
{
//Config examples
//this.Plugins.Add(new PostmanFeature());
//this.Plugins.Add(new CorsFeature());
Plugins.Add(new ValidationFeature());
container.RegisterValidators(typeof(AnalysisRunRequestValidator).Assembly);
.......
.......
.......
.......
container.Register<AnalysisPartValidator>(c => new AnalysisPartValidator(
c.Resolve<AnalysisDealPartLinkingModelEventSetValidator>(),
c.Resolve<AnalysisOutputSettingsPartValidMetaRisksValidator>(),
c.Resolve<AnalysisOutputSettingsGroupPartValidMetaRisksValidator>(),
c.Resolve<AnalysisDealPartCollectionValidator>(),
c.Resolve<AnalysisPortfolioPartCollectionValidator>(),
c.Resolve<UniqueCombinedOutputSettingsPropertiesValidator>()))
.ReusedWithin(ReuseScope.None);
container.Register<AnalysisRunRequestValidator>(c => new AnalysisRunRequestValidator(c.Resolve<AnalysisPartValidator>()))
.ReusedWithin(ReuseScope.None);
_log.Debug("ApiServerHttpHost Configure ok");
SetConfig(new HostConfig
{
DefaultContentType = MimeTypes.Json
});
}}
}
So I then hit this endpoint with this JSON
{
"Analysis": {
//Not important for discussion
//Not important for discussion
//Not important for discussion
//Not important for discussion
},
"CreatedBy": "frank"
}
And I get this response in PostMan tool (which I was expecting)
So that's all good.
.NET Core example
So now lets see what its like in .Net core example.
Lets start with the request dto
using System;
namespace ServiceStack.Demo.Model.Core
{
[Route("/analysis/run", "POST")]
public class AnalysisRunRequest : BaseRequest, IReturn<AnalysisRunResponse>
{
public AnalysisDto Analysis { get; set; }
}
}
Which uses this base request object
using System;
using System.Collections.Generic;
using System.Text;
namespace ServiceStack.Demo.Model.Core
{
public abstract class BaseRequest
{
public string CreatedBy { get; set; }
}
}
And here is the same validator we used from .NET 4.6.2 example, but in my .Net Core code instead
using System;
using System.Collections.Generic;
using System.Text;
using ServiceStack.Demo.Model.Core;
using ServiceStack.FluentValidation;
namespace ServiceStack.Demo.Core.Validators
{
public class AnalysisRunRequestValidator : AbstractValidator<AnalysisRunRequest>
{
public AnalysisRunRequestValidator(AnalysisDtoValidator analysisDtoValidator)
{
RuleFor(analysis => analysis.CreatedBy)
.Must(HaveGoodCreatedBy)
.WithMessage("CreatedBy MUST be 'sbarber'")
.WithErrorCode(ErrorCodes.ValidationErrorCode);
}
private bool HaveGoodCreatedBy(AnalysisRunRequest analysisRunRequest, string createdBy)
{
return createdBy == "sbarber";
}
}
}
And here is my host code for .Net core example
using System;
using System.Collections.Generic;
using System.Text;
using Funq;
using ServiceStack.Demo.Core.Api.Analysis;
using ServiceStack.Demo.Core.IOC;
using ServiceStack.Demo.Core.Services;
using ServiceStack.Demo.Core.Validators;
using ServiceStack.Text;
using ServiceStack.Validation;
namespace ServiceStack.Demo.Core.Api
{
public class ApiServerHttpHost : AppHostBase
{
public static string RoutePrefix => "analysisapi";
public ApiServerHttpHost()
: base(typeof(ApiServerHttpHost).FullName, typeof(AnalysisServiceStackService).GetAssembly())
{
Console.WriteLine("ApiServerHttpHost constructed");
}
public override void SetConfig(HostConfig config)
{
base.SetConfig(config);
JsConfig.TreatEnumAsInteger = true;
JsConfig.EmitCamelCaseNames = true;
JsConfig.IncludeNullValues = true;
JsConfig.AlwaysUseUtc = true;
JsConfig<Guid>.SerializeFn = guid => guid.ToString();
JsConfig<Guid>.DeSerializeFn = Guid.Parse;
config.HandlerFactoryPath = RoutePrefix;
var exceptionMappings = new Dictionary<Type, int>
{
{typeof(NullReferenceException), 400},
};
config.MapExceptionToStatusCode = exceptionMappings;
Console.WriteLine("ApiServerHttpHost SetConfig ok");
}
public override void Configure(Container container)
{
//Config examples
//this.Plugins.Add(new PostmanFeature());
//this.Plugins.Add(new CorsFeature());
Plugins.Add(new ValidationFeature());
container.RegisterValidators(typeof(ApiServerHttpHost).GetAssembly());
container.RegisterAutoWiredAs<DateProvider, IDateProvider>()
.ReusedWithin(ReuseScope.Container);
container.RegisterAutoWiredAs<FakeRepository, IFakeRepository>()
.ReusedWithin(ReuseScope.Container);
container.Register<LifetimeScopeManager>(cont => new LifetimeScopeManager(cont))
.ReusedWithin(ReuseScope.Hierarchy);
container.Register<DummySettingsPropertiesValidator>(c => new DummySettingsPropertiesValidator(c.Resolve<LifetimeScopeManager>()))
.ReusedWithin(ReuseScope.None);
container.Register<AnalysisDtoValidator>(c => new AnalysisDtoValidator(
c.Resolve<DummySettingsPropertiesValidator>()))
.ReusedWithin(ReuseScope.None);
container.Register<AnalysisRunRequestValidator>(c => new AnalysisRunRequestValidator(c.Resolve<AnalysisDtoValidator>()))
.ReusedWithin(ReuseScope.None);
SetConfig(new HostConfig
{
DefaultContentType = MimeTypes.Json
});
}
}
}
And this is me trying to now hit the .Net Core endpoint with the same bad JSON payload as demonstrated above with the .Net 4.6.2 example, which gave correct Http response (i.e included error that I was expecting in response)
Anyway here is payload being sent to .Net Core endpoint
{
"Analysis": {
//Not important for discussion
//Not important for discussion
//Not important for discussion
//Not important for discussion
},
"CreatedBy": "frank"
}
Where we can see that we are getting into the .Net Core example validator code just fine
But this time I get a very different Http response (one that I was not expecting at all). I get this
It can be seen that we do indeed get the correct Status code of "400" (failed) which is good. But we DO NOT get anything about the validation failure at all.
I was expecting this to give me the same http response as the original .Net 4.6.2 example above.
But what I seem to be getting back is the JSON representing the AnalysisRunResponse. Which looks like this
using System;
using System.Collections.Generic;
using System.Text;
namespace ServiceStack.Demo.Model.Core
{
public class AnalysisRunResponse : BaseResponse
{
public Guid AnalysisUid { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace ServiceStack.Demo.Model.Core
{
public abstract class BaseResponse
{
public ResponseStatus ResponseStatus { get; set; }
}
}
I thought the way ServiceStack works (in fact that is how it works for ALL our existing .Net 4.6.2 code) is that the validation is done first, if AND ONLY if it passes validation is the actual route code run
But this .Net core example seems to not work like that.
I have a break point set in Visual Studio for the actual route and Console.WriteLine(..) but that is never hit and I never see the result of the Console.WriteLine(..)
What am I doing wrong?
This doesn't seem to any longer an issue with the latest v5 of ServiceStack that's now available on MyGet.
As sending this request:
Is returning the expected response:
The .NET Core packages are merged with the main packages in ServiceStack v5 so you'll need to remove the .Core prefix to download them, i.e:
<PackageReference Include="ServiceStack" Version="5.*" />
<PackageReference Include="ServiceStack.Server" Version="5.*" />
You'll also need to add ServiceStack's MyGet feed to fetch the latest ServiceStack v5 NuGet packages which you can do by adding this NuGet.config in the same folder as your .sln:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="ServiceStack MyGet feed" value="https://www.myget.org/F/servicestack" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
</configuration>

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.

CKfinder- Dynamic User folder -Asp.net MVC 5

I am using Asp.NET MVC 5 to build a web application. I downloaded Ckeditor and CKfinder Connector for ASP.NET. I was able to follow the instructions and get Ckeditor and Ckfinder integration to work.
I am trying to figure out how I can have dynamic folder directory in CkFinder per logged in user. According to the instructions provided in http://docs.cksource.com/ckfinder3-net/howto.html#howto_private_folders it tells you to do that in connectorBuilder .SetRequestConfiguration. The problem is that ConnectorBuilder is being setup on the startup and the user logs in after that?
Here is the code that i have now where everything works except the icons
using DearColleagueV2.Models;
[assembly: Microsoft.Owin.OwinStartup(typeof(DearColleagueV2.Startup))]
namespace DearColleagueV2
{
using System.Configuration;
using CKSource.CKFinder.Connector.Config;
using CKSource.CKFinder.Connector.Core.Builders;
using CKSource.CKFinder.Connector.Core.Logs;
using CKSource.CKFinder.Connector.Host.Owin;
using CKSource.CKFinder.Connector.Logs.NLog;
using CKSource.CKFinder.Connector.KeyValue.EntityFramework;
using CKSource.FileSystem.Dropbox;
using CKSource.FileSystem.Local;
using System;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Owin;
using Microsoft.Owin.Security;
using CKSource.CKFinder.Connector.Core.Acl;
using System.Collections.Generic;
using CKSource.CKFinder.Connector.Core.Authentication;
using System.Threading.Tasks;
using CKSource.CKFinder.Connector.Core;
using System.Threading;
using System.Security.Cryptography;
using System.Text;
public partial class Startup
{
public void Configuration(IAppBuilder builder)
{
LoggerManager.LoggerAdapterFactory = new NLogLoggerAdapterFactory();
ConfigureAuthForIdentity(builder);
RegisterFileSystems();
var connectorBuilder = ConfigureConnector();
var connector = connectorBuilder.Build(new OwinConnectorFactory());
builder.Map("/CKFinder/connector", builder1 => builder1.UseConnector(connector));
}
private void ConfigureAuthForIdentity(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.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,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
// Enables the application to remember the second login verification factor such as phone or email.
// Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
// This is similar to the RememberMe option when you log in.
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
}
public ConnectorBuilder ConfigureConnector()
{
var connectorBuilder = new ConnectorBuilder();
connectorBuilder
.SetRequestConfiguration(
(request, config) =>
{
//config.AddProxyBackend("local", new LocalStorage(#"MyFiles"));
var userName = request.Principal?.Identity?.Name;
if (userName != null)
{
var sha = new SHA1CryptoServiceProvider();
var hash = sha.ComputeHash(Encoding.UTF8.GetBytes(userName));
var folderName = BitConverter.ToString(hash).Replace("-", string.Empty);
config.AddProxyBackend("local", new LocalStorage(#"c:\files"));
config.AddResourceType("private", resourceBuilder => resourceBuilder.SetBackend("local", folderName));
config.SetThumbnailBackend("local", "thumbs");
config.AddAclRule(new AclRule(
new StringMatcher("*"), new StringMatcher("/"), new StringMatcher("*"),
new Dictionary<Permission, PermissionType>
{
{ Permission.FolderView, PermissionType.Allow },
{ Permission.FolderCreate, PermissionType.Allow },
{ Permission.FolderRename, PermissionType.Allow },
{ Permission.FolderDelete, PermissionType.Allow },
{ Permission.FileView, PermissionType.Allow },
{ Permission.FileCreate, PermissionType.Allow },
{ Permission.FileRename, PermissionType.Allow },
{ Permission.FileDelete, PermissionType.Allow },
{ Permission.ImageResize, PermissionType.Allow },
{ Permission.ImageResizeCustom, PermissionType.Allow }
}));
}
})
.SetAuthenticator(new MyAuthenticator());
return connectorBuilder;
}
private static void RegisterFileSystems()
{
FileSystemFactory.RegisterFileSystem<LocalStorage>();
FileSystemFactory.RegisterFileSystem<DropboxStorage>();
}
}
public class MyAuthenticator : IAuthenticator
{
public Task<CKSource.CKFinder.Connector.Core.Authentication.IUser> AuthenticateAsync(ICommandRequest commandRequest, CancellationToken cancellationToken)
{
var user = new User(true, null);
return Task.FromResult((CKSource.CKFinder.Connector.Core.Authentication.IUser)user);
}
}
}
The SetRequestConfiguration method of the ConnectorBuilder class accepts an action that will be called for each request.
The code from the example you linked, although defined during startup, will be executed for every request.
Additionally you should make sure that the user is already logged in when she is trying to use CKFinder. For example:
public class Startup
{
public void Configuration(IAppBuilder app)
{
var connectorFactory = new OwinConnectorFactory();
var connectorBuilder = ...
var connector = connectorBuilder.Build(connectorFactory);
app.UseCookieAuthentication(
/*
* Your CookieAuthenticationOptions that will redirect anonymous
* users to the login page
*/
);
app.UseConnector(connector);
}
}
About missing thumbnails, you should add at least one allowed thumbnail size. Just add something like config.SetThumbnailSizes(new SizeAndQuality(100, 100, new ImageQuality(80))); to the action executed in SetRequestConfiguration.

Why isn't my Azure Website accepting OAuth tokens?

I want my application to accept OAuth tokens when hosted using Azure Websites. I have the following:
web.config of web app
<appSettings>
<add key="ida:Realm" value="https://example.com/development" />
<add key="ida:AudienceUri" value="https://example.com/development" />
<add key="ida:Tenant" value="example.com" />
</appSettings>
Startup.cs of web app
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.SignalR;
using Microsoft.Owin;
using Owin;
using Microsoft.Owin.Security.ActiveDirectory;
using System.Configuration;
[assembly: OwinStartup(typeof(MyApplication.Web.Startup))]
namespace MyApplication.Web
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters()
{
ValidAudience = ConfigurationManager.AppSettings["ida:AudienceUri"]
},
Tenant = ConfigurationManager.AppSettings["ida:Tenant"]
});
}
}
}
Main.cs
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Globalization;
using System.Net.Http;
using System.Net.Http.Headers;
void Main()
{
var clientId = #"GUIDGUIDGUID";
var key = #"KEYKEYKEYKEYKEY";
var aadInstance = "https://login.windows.net/{0}";
var tenant = "example.com";
var authContext = new AuthenticationContext(String.Format(CultureInfo.InvariantCulture, aadInstance, tenant), true);
var credential = new ClientCredential(clientId, key);
authContext.TokenCache.Clear();
var token = authContext.AcquireToken(#"https://example.com/development", credential);
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.AccessToken);
var response = client.GetAsync(#"https://app.example.com/").Result;
var responseText = response.Content.ReadAsStreamAsync().Result;
Console.Write(new StreamReader(responseText).ReadToEnd());
}
}
Can anyone provide some guidance?
So it turns out that I was using the Uri class to validate the App ID URI. Problem is, it adds a trailing slash onto to the end, which causes problems. As soon as I started using the string class to store the App ID URI, it was fine.
So make sure you are using exactly the values seen in Azure AD!

Resources