How to configure an mvc client running on .Net Framework 4.7.1 to Authenticate with IdentityServer4 (3.1) running on .Net Core - identityserver4

I am not sure how to configure an mvc client running on .Net Framework 4.7.1 to Authenticate with IdentityServer4 (3.1) running on .Net Core.
I have successfully authenticated clients running on .net core against IdentityServer4 before but not a client running on .Net Framework. I can't upgrade this client to .net core unfortunately.
Basically, I am not sure how to do this on the mvc client:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://myIdentityServer:4532";
options.ClientId = "MVC_Net_Framework";
options.ClientSecret = "mysecret";
options.ResponseType = "code";
options.Scope.Add("myScope");
options.SaveTokens = true;
});
}

you need to use OwinStartup class . add partial class Startup in root of your project as
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Microsoft.Owin;
using Owin;
using Microsoft.Owin.Cors;
using System.Web.Http;
using System.Web.Mvc;
using System.Configuration;
[assembly: OwinStartup(typeof(MCVAppNet7.Startup))]
namespace MCVAppNet7
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
var services = new ServiceCollection();
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
ConfigureAuth(app);
// For Access-Control-Allow-Origin
app.UseCors(CorsOptions.AllowAll);
}
}
}
after this create a new file "Startup.Auth.cs" in "App_Start" folder and inside this create partial Startup class
using System.Configuration;
using Owin;
using Microsoft.Owin.Security.Cookies;
using IdentityServer3.AccessTokenValidation;
using System;
namespace MCVAppNet7
{
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
try
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "",
ClientId = "",
AuthenticationType = "Bearer",
RequiredScopes = new[] { "" },
ValidationMode = "",
PreserveAccessToken = true,
RequireHttps = ""
});
}
catch (Exception ex)
{
throw ex;
}
}
}
}
install these package from from NuGet
Microsoft.Owin
Microsoft.Owin.Security.OAuth
Microsoft.Owin.Host.SystemWeb
IdentityModel
IdentityServer3.Contrib.AccessTokenValidation
I'm using IdentityServer3.Contrib.AccessTokenValidation and it's working for me but it might work with IdentityServer4.AccessTokenValidation and more info here
https://learn.microsoft.com/en-us/aspnet/aspnet/overview/owin-and-katana/owin-startup-class-detection
ASP.NET Web Api - Startup.cs doesn't exist
https://github.com/IdentityServer/IdentityServer4/issues/4188

Related

Why do accessing Client certs for AAD Auth require console Apps to Run as Administrator? Does it have too?

I have a basic console app that I want to access Azure Key Vault for a connection string. Been told over and over to use client certs in production environment for AAD Authentication. Problem is doing so seems to force the console app to be "Run as Administrator" triggering the UAC, making it impossible to be scheduled unattended.
Relevant code:
using Azure.Extensions.AspNetCore.Configuration.Secrets;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
using System.Security.Cryptography.X509Certificates;
var host = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
...
.
})
.UseSerilog()
.ConfigureAppConfiguration(config =>
{
...
var credential = new ClientCertificateCredential(tenantId, clientID, GetCertificate(thumbPrint));
var client = new SecretClient(kvUri, credential);
config.AddAzureKeyVault(client, new AzureKeyVaultConfigurationOptions());
})
.Build();
static X509Certificate2 GetCertificate(string thumbprint)
{
var store = new X509Store(StoreName.Root);
try
{
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
var collection = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
if (collection.Count == 0) throw new System.Exception("Certificate is not installed.");
return collection[0];
}
finally
{
store.Close();
}
}
At this point I'm down to making it a Windows Background Service...

POST method is working on POSTMAN but results are not being shown in SQL Server

I am trying to connect a Web API from Visual Studio which is connected with database using database first model but for some reason any data that I am trying to enter using POSTMAN POST method is not reflecting
back in SQL Server.
Here is the code that I wrote in VS in my Web API project:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using try43.Models;
namespace try43.Controllers
{
public class LoginController : ApiController
{
[HttpPost]
public HttpResponseMessage Login([FromBody] Customer customer)
{
ProjectDbContext db = new ProjectDbContext();
db.Customers.Add(customer);
return Request.CreateResponse(HttpStatusCode.OK, customer);
}
}
}
and this is WebApIConfig.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Headers;
using System.Web.Http;
using System.Web.Http.Cors;
namespace try43
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
}
}
}
Postman is able to show this page:
You're missing
db.SaveChanges();
To tell EF to save your changes.

In WASM Blazor using Azure Active Directory, how do I bypass Auth during development

Authenticating WASM Blazor against Azure Active Directory is covered nicely by Microsoft in their walkthroughs. What they don't cover is the development workflow afterwards. Being a compiled application, every change to the UI is a painful stop-recompile-start process, which is then compounded by an AAD login process.
How do we streamline this and set a fake set of credentials during development?
This approach works for me, for now, but I am keen to see what others do. Note this is primarily for development, but I could look to extend this for integration tests (which is next on my list).
In the client, make yourself a fake AuthenticationStateProvider to replace the Remote authentication one you normally use.
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
namespace Blah.Client
{
public class FakeAuthStateProvider : AuthenticationStateProvider, IAccessTokenProvider
{
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, ">> TEST USER <<"),
new Claim("directoryGroup","abc4567-890-1234-abcd-1234567890abc") //Should match your group you use to determine a policy
}, "Fake authentication type");
var user = new ClaimsPrincipal(identity);
return Task.FromResult(new AuthenticationState(user));
}
public async ValueTask<AccessTokenResult> RequestAccessToken()
{
return new AccessTokenResult(AccessTokenResultStatus.Success, new AccessToken() { Expires = DateTime.Now + new TimeSpan(365,0,0,0) }, "");
}
public async ValueTask<AccessTokenResult> RequestAccessToken(AccessTokenRequestOptions options)
{
return new AccessTokenResult(AccessTokenResultStatus.Success, new AccessToken() { Expires = DateTime.Now + new TimeSpan(365, 0, 0, 0) }, "");
}
}
}
In the client program.cs, switch out the auth when in debug:
#if DEBUG
SetupFakeAuth(builder.Services);
#else
builder.Services.AddMsalAuthentication<RemoteAuthenticationState, CustomUserAccount>(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("api://1234567-890-1234-abcd-1234567890abc/API.Access");
})
.AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, CustomUserAccount, CustomAccountFactory>();
#endif
.....
private static void SetupFakeAuth(IServiceCollection services)
{
//https://github.com/dotnet/aspnetcore/blob/c925f99cddac0df90ed0bc4a07ecda6b054a0b02/src/Components/WebAssembly/WebAssembly.Authentication/src/WebAssemblyAuthenticationServiceCollectionExtensions.cs#L28
services.AddOptions();
services.AddAuthorizationCore();
services.TryAddScoped<AuthenticationStateProvider, FakeAuthStateProvider>();
services.TryAddTransient<BaseAddressAuthorizationMessageHandler>();
services.TryAddTransient<AuthorizationMessageHandler>();
services.TryAddScoped(sp =>
{
return (IAccessTokenProvider)sp.GetRequiredService<AuthenticationStateProvider>();
});
services.TryAddScoped<IAccessTokenProviderAccessor, FakeAccessTokenProviderAccessor>();
services.TryAddScoped<SignOutSessionStateManager>();
}
...
And define the FakeAuthState provider, which is just a copy of the internal class Microsoft register:
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal
{
internal class FakeAccessTokenProviderAccessor : IAccessTokenProviderAccessor
{
private readonly IServiceProvider _provider;
private IAccessTokenProvider _tokenProvider;
public FakeAccessTokenProviderAccessor(IServiceProvider provider) => _provider = provider;
public IAccessTokenProvider TokenProvider => _tokenProvider ??= _provider.GetRequiredService<IAccessTokenProvider>();
}
}
This should result in a logged in user on the client that has a name and Scopes as usual.
Server side:
in Startup.cs
#if DEBUG
services.AddSingleton<IPolicyEvaluator, FakePolicyEvaluator>();
#else
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd"));
#endif
and a new class:
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Policy;
using Microsoft.AspNetCore.Http;
namespace Blah.Server
{
public class FakePolicyEvaluator : IPolicyEvaluator
{
public virtual async Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
{
const string testScheme = "FakeScheme";
var principal = new ClaimsPrincipal();
principal.AddIdentity(new ClaimsIdentity(new[] {
new Claim("Permission", "CanViewPage"),
new Claim("Manager", "yes"),
new Claim(ClaimTypes.Role, "Administrator"),
new Claim(ClaimTypes.NameIdentifier, "John")
}, testScheme));
return await Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(principal,
new AuthenticationProperties(), testScheme)));
}
public virtual async Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy,
AuthenticateResult authenticationResult, HttpContext context, object resource)
{
return await Task.FromResult(PolicyAuthorizationResult.Success());
}
}
}
Hope that helps someone. I'll now look to improve this and make it work in testing scenarios.

HTTP Error 500 when deploying ASP.NET Core 2.2 to additional Azure Web App

I have already deployed the application to a Azure web app that works just fine. I am using an ASP.NET Core 2.2 application with MVC connected to a SQL server located on Azure. I am working with dotnet SDK version 3.1.301.
The application has a React client. I am trying to make the application multi - tenancy, therefore, each web app has its own Azure Key Vault but a single database. All of the environment variables are located within the key vault, including the connection string to the db.
I have created an additional web app on Azure (this one is for production) connected to an additional Key Vault. After deploying to this web app, I get a HTTP 500 error. In the web activity logs I do not receive any errors. I am using Microsoft Visual 2019 to deploy the applications.
My question is; I have followed all the same steps for the first deployment and I would really like to know why, when spinning up a new app with a new key vault, why I am not getting any errors?
Program.cs
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
if (context.HostingEnvironment.IsProduction())
{
var builtConfig = config.Build();
var keyVaultUri = builtConfig.GetValue<string>("KEY_VAULT_URI"); //Connection to KEY VAULT
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
config.AddAzureKeyVault(keyVaultUri, keyVaultClient, new DefaultKeyVaultSecretManager());
}
}
).UseStartup<Startup>();
}
Startup.cs
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.AddTransient<ILearnerService, LearnerService>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddJsonOptions(options =>
// Fixed Unexpected end of JSON
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
services.AddDbContext<DataContext>(
option => option.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddAutoMapper();
services.AddCors();
// Configure DI for application services
services.AddScoped<ILearnerService, LearnerService>();
// In production, the React files will be served from this directory **********
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
// Configure strongly typed settings objects
var appSettingsSection = Configuration;
services.Configure<AppSettings>(appSettingsSection);
services.AddMultiTenancy()
.WithResolutionStrategy<HostResolutionStrategy>()
.WithStore<InMemoryTenantStore>();
// Configure jwt authentication
var appSettings = appSettingsSection.Get<AppSettings>();
var key = Encoding.ASCII.GetBytes(appSettings.Secret);
var encryptionKey = Encoding.UTF8.GetBytes(appSettings.EncryptionSecret);
services.AddAuthorization(options =>
{
options.AddPolicy(TenantFeatures.Inbox, policyBuilder =>
{
policyBuilder.RequireAuthenticatedUser()
.RequireAssertion(context =>
context.User.HasClaim(TenantFeatures.Inbox, "user.Stores.Companies"))
.Build();
});
});
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var learnerService = context.HttpContext.RequestServices.GetRequiredService<ILearnerService>();
var userId = int.Parse(context.Principal.Identity.Name);
var user = learnerService.GetById(userId);
if (user == null)
{
// Return unauthorized if user no longer exists
context.Fail("Unauthorized");
}
// user.Stores.Companies
var tenantInfo = Configuration.GetValue<string>("tesco-training-net");
if (tenantInfo.Contains(TenantFeatures.Inbox))
{
Claim claim = new Claim(TenantFeatures.Inbox, "user.Stores.Companies");
((ClaimsIdentity)context.Principal.Identity).AddClaim(claim);
}
if (tenantInfo.Contains(TenantFeatures.DynamicCV))
{
Claim claim = new Claim(TenantFeatures.DynamicCV, "user.Stores.Companies");
((ClaimsIdentity)context.Principal.Identity).AddClaim(claim);
}
return Task.CompletedTask;
}
};
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
RequireSignedTokens = true,
// Fixed [Authorize] decrypts the tokens
TokenDecryptionKey = new SymmetricSecurityKey(encryptionKey),
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddApplicationInsightsTelemetry(Configuration["APPINSIGHTS_INSTRUMENTATIONKEY"]);
services.AddApplicationInsightsTelemetry(Configuration["APPINSIGHTS_INSTRUMENTATIONKEY"]);
}
// 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())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
app.UseAuthentication();
app.UseMultiTenancy();
app.UseMvc();
//app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
}
EDIT
Within the Application logs, I am getting this:
IIS was not able to access the web.config file for the Web site or
application. This can occur if the NTFS permissions are set incorrectly.
IIS was not able to process configuration for the Web site or application.
The authenticated user does not have permission to use this DLL. The request is mapped to a managed handler but the .NET Extensibility Feature is not installed. Things you can try: Ensure that the NTFS permissions for the web.config file are correct and allow access to the Web server's machine account. Check the event logs to see if any additional information was logged. Verify the permissions for the DLL. Install the .NET Extensibility feature if the request is mapped to a managed handler. Create a tracing rule to track failed requests for this HTTP status code. For more information about creating a tracing rule for failed requests, click here.

Pass JWT token from angular HttpClient to access secure .NET Core Web API

I need to call secure Web API from Angular 9 application by presenting the token. I am using Angular with .NET CORE 3.1 Web API. I have managed to generate Azure B2C token but stuck to call secure web api as I got CORS error.
Angular component calling Web API end-point
testAPI1(){
console.log("calling test API ...");
const myheaders = new HttpHeaders({
'Content-Type': 'application/json; charset=utf-8',
'Authorization': this.authService.accessToken
});
this.http.get('https://localhost:5001/txn/v1/Dashboard/GetMessage', {headers: myheaders})
.subscribe((data)=>{
console.warn(data);
})
}
Auth Service
#Injectable()
export class AuthService implements OnInit{
constructor(
private oauthService: OAuthService,
private router: Router
){// other code}
public get accessToken() {
return this.oauthService.getAccessToken();
}
Web API controller & endpoint
[Authorize]
[Route("txn/v1/[controller]/[action]")]
[EnableCors("CorsPolicy")]
[ApiController]
public class DashboardController : ControllerBase
{
[HttpGet]
public ActionResult<HelloMessage> GetMessage()
{
var result = new HelloMessage()
{
GivenName = "james",
ReturnMessage = "Dashboard# Hello, Welcome to Digital tech"
};
return result;
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//JWT Authentication
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(jwtConfig =>
{
jwtConfig.Audience = Configuration["AzureAdB2C:ResourceId"];
jwtConfig.Authority = $"{Configuration["AzureAdB2C:Instance"]}{Configuration["AzureAdB2C:TanantId"]}";
jwtConfig.RequireHttpsMetadata = false;
jwtConfig.SaveToken = true;
jwtConfig.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer =true,
ValidateAudience = true,
ValidateLifetime = true
};
});
//CORS policy
services.AddCors(options =>
options.AddPolicy("CorsPolicy", builder => builder.AllowAnyOrigin()));
error
Policies for CORS can be a bit finicky. So I would recommend maybe trying for a pretty open CORS policy (Which isn't too dangerous given you are using header authentication and not a cookie).
So your configure services method should look like so :
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
}
And then your Configure method should be something like :
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseCors( options => options.WithOrigins("http://example.com").AllowAnyMethod() );
app.UseMvc();
}
Note that the order inside the Configure method is important. The call to CORS must be relatively early on, if not the first middleware in your pipeline.
If that works, then work backwards to slowly add policies and see which one breaks. CORS can be really finicky so it works better to allow everything in a basic example and then add thing slowly in.
More reading here : https://dotnetcoretutorials.com/2017/01/03/enabling-cors-asp-net-core/

Resources