Im very young in MVC apps
i would like to create a webservice providing an Identity to an external MVC app.
Benefits of exposing ASP.NET Core Identity as a service:
The actual app code is much simpler and decoupled from its identity concerns
Support for established authentication standards and patterns simplifies security concerns and builds trust
The identity service can live in a separate process
Reuse user identities across multiple apps
Is that possible ? Any ideas I can do that ?
This is possible.
This is how we do it.
We use IdentityServer4 to generate JWT tokens to clients. We have created a simple MVC project that has the followng simple startup file that can give you an idea.
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityServer()
.AddSigningCredential(new X509Certificate2(Path.Combine(".", "cert", "token-cert.pfx"), "cert-password"))
.AddInMemoryApiResources(Config.GetApiResources())
.AddClientStore<CustomClientStore>();
string connectionString = Configuration.GetConnectionString("DefaultConnection");
// a data service to fetch user data from database
services.AddTransient<IUserDataMapper>(s => new UserDataMapper(connectionString));
}
// 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();
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseIdentityServer();
app.Run(async (context) =>
{
await context.Response.WriteAsync("ACME Auth Token API v1.0");
});
}
You can find a detailed explanation of IdentityServer4 at https://identityserver4.readthedocs.io/en/release/quickstarts/1_client_credentials.html#defining-the-api
Related
When configuring OpenIddict encryption keys (both for signing and validating signed "access tokens"), this is done in the Startup of the application (public void ConfigureServices(IServiceCollection services) {...}), where the services (DI) are still being registered.
So, assuming that the asymmetric keys I want to use to sign/validate the tokens are stored on Azure Key Vault, and in the context of following the good practices and principles (like SOLID) to use Dependency Injection to implement a client to access my Azure Key Vault (instead of a static helper class that can just be called in the Startup, as done in this example), my question is...
Is there a way to configure OpenIddict Encryption Keys using Dependency Injection, maybe using a function or handler that would be run after the setup is done?
Example of configuring OpenIddict Server:
public void ConfigureServices(IServiceCollection services)
{
services.AddOpenIddict()
// [...]
.AddServer(options =>
{
// [...]
var key = new Microsoft.IdentityModel.Tokens.RsaSecurityKey(rsaKey);
options.AddSigningKey(key);
// [...]
});
}
Example of configuring a Resource API with OpenIddict validation:
public void ConfigureServices(IServiceCollection services)
{
services.AddOpenIddict()
.AddValidation(options =>
{
// [...]
var key = new Microsoft.IdentityModel.Tokens.RsaSecurityKey(rsaKey);
options.AddEncryptionKey(key);
// [...]
});
}
The only way of using DI I found is not recommended at all, which would be registering the services I need first, and manually calling services.BuildServiceProvider() to have a ServiceProvider to resolve the type I need and its dependencies, example:
public void ConfigureServices(IServiceCollection services)
{
services.AddOpenIddict()
// [...]
.AddServer(options =>
{
// [...]
ServiceProvider serviceProvider = services.BuildServiceProvider();
var azureKeyVaultClient = serviceProvider.GetService<IAzureKeyVaultClient>();
var rsaKey = azureKeyVaultClient.GetKey(myKeyName).ToRSA();
var key = new Microsoft.IdentityModel.Tokens.RsaSecurityKey(rsaKey);
options.AddSigningKey(key);
// [...]
});
}
But, as you can see below, besides the fact it is a very odd approach, it's also not recommended because "Calling 'BuiIdServiceProvider' from application code results in an additional copy of singleton services being created.".
So, does anyone know how I can use DI when configuring OpenIddict keys?
Although I am very happy that IdentityServer4 exists and makes life I with regards to authentication much easier in many ways, I've stumbled onto the problem and the many discussions of adding roles to claims within the community.
My requirements are simple:
I develop apps (xamarin forms) who require authentication and authorization
I need a system to store the identity of my users (name, pwd, roles, phone…) -> ASP.net Identity
I need a system to authenticate my users -> IdentityServer 4
Roles per user are very limited (user/admin) and don’t change
I need an API backend and an MVC admin site (asp.net core)
I want to limit access to some API/MVC controllers using [Authorize(Roles = "Admin")]
I've spent countless hours trying different configurations to get asp.net Identity roles to be passed to my MVC application after authentication but without luck. The purpose is as described in point 6.
I've also spent countless hours reading but I have a feeling that the implementation IdentityServer4.AspIdentity regarding roles has changed a lot since the version v1.0.0.
After reading a lot about this, it remains unclear how to actually implement this as it seems that some of the solution described only 2 months ago are no longer valid.
So, for now I believe there are 2 paths:
Role into the identity token by only requesting an identity token
and using AlwaysIncludeInIdToken?
Let the client retrieve the roles
using the userinfo endpoint and somehow inject them into the
httpcontext(?) alowing mvc to check using [Authorize(Roles =
"Admin")]?
Anyway, that's my assumptions.
So, please help out and explain/document in detail so we can start implementing this in a durable way? Some workable examples would be great as well.
So, after investigation I’ve come up with 2 methods to do this:
Include Roles/other claims on the Identity Server Side
Include Roles/other claims on the Client side
Include on Identity Server Side
ravi punjwani provided the answer in ‘How to add additional claims to be included in the access_token using ASP.Net Identity with IdentityServer4. His solution is still in draft but the solution allows you to add any claim before the token is send back to the client. This is the link: How to add additional claims to be included in the access_token using ASP.Net Identity with IdentityServer4
Include on Client side
This one is a little tougher as it involves adding ‘IClaimsTransformer’ in the request pipeline of the client. The result is that per request, the Claimstransformer will retrieve the claims for the user and add it to the User Identity claims (the token). The set-up of the Claimstransformer is not easy as it’s tricky to get DI to work but after lots of research the celow solution does it for me.
The Custom ClaimsTransformer class does the transformation in the Middleware:
public class KarekeClaimsTransformer : IClaimsTransformer
{
private readonly UserManager _userManager;
public KarekeClaimsTransformer(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
{
if (context.Principal.Identity.IsAuthenticated)
{
Claim userId = context.Principal.FindFirst("sub");
if (context.Principal.FindFirst("role") == null && userId != null)
{
ApplicationUser user = await _userManager.FindByIdAsync(userId.Value);
var roles = await _userManager.GetRolesAsync(user);
foreach (var role in roles)
{
((ClaimsIdentity)context.Principal.Identity).AddClaim(new Claim(JwtClaimTypes.Role, role,
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role"));
}
}
}
return Task.FromResult(context.Principal).Result;
}
}
In the Client start-up class you need to Add it to the scope in ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
...
services.AddScoped<IClaimsTransformer, KarekeClaimsTransformer>();
// Add framework services.
services.AddMvc();
}
Lastly, add in Configure:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "http://localhost:5000",
RequireHttpsMetadata = false,
ApiName = "api1"
});
app.UseClaimsTransformation((context) =>
{
IClaimsTransformer transformer = context.Context.RequestServices.GetRequiredService<IClaimsTransformer>();
return transformer.TransformAsync(context);
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "api/{controller}/{action?}/{id?}");
});
}
I've created a simple ASPNET Core 1.0 MVC app, which I am trying to deploy to Azure using Visual Studio. I am able to run this app locally on my machine using IIS Express, and navigate to my default route and display the page. However, in Azure I always get a 500 error every time, and at this point I am at a loss for how to get additional information.
I've enabled detailed request logging in my Azure app, but it doesn't really seem to tell me much.
ModuleName="AspNetCoreModule", Notification="EXECUTE_REQUEST_HANDLER", HttpStatus="500", HttpReason="Internal Server Error", HttpSubStatus="0", ErrorCode="The operation completed successfully.
(0x0)", ConfigExceptionInfo=""
I've stripped down my Startup configuration to the bare essentials
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggingFactory)
{
loggingFactory.AddConsole();
loggingFactory.AddDebug();
app.UseMvc(routes => {
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}"
);
});
}
Something must be blowing up in the MVC pipeline but I have no idea how to add more visibility. What can I do to get more information?
And in case it matters, this is my Program.cs
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
Try setting ASPNETCORE_ENVIRONMENT to Development to display full exception message on the error page.
Remember to turn it off when finished, otherwise you will leak error information.
I would like to use Openiddict OAuth to protect api endpoints in my ASP.NET Core 1.0 Web Application. The api endpoints will be called by a phone app and users must login with username and password.
The flow goes like this:
User can register and login via web application: https://www.domain.com
User install phone app, and they can login and register using the phone app. Login, registration and data access is done via api endpoints: Example: https://www.domain.com/api/service/getsomedata
How can I configure Openiddict OAuth so I can protect the API endpoints using OAuth?
How can I configure Openiddict OAuth so I can protect the API endpoints using OAuth?
Your scenario sounds like a good candidate for the simple "resource owner password credentials" grant, which is basically the OAuth2 equivalent of basic or forms authentication.
Here's what I'd recommend:
Create a new AccountController/RegistrationController API controller responsible of creating new accounts:
Since the user account doesn't exist at this stage, you can't use token authentication here (just like the default AccountController.Register template cannot require cookies authentication before the user is registered).
Configure OpenIddict to enable the token endpoint and allow the resource owner password credentials grant:
services.AddOpenIddict<ApplicationDbContext>()
// Disable the HTTPS requirement during development.
.DisableHttpsRequirement()
// Enable the token endpoint, required to use
// the resource owner password credentials grant.
.EnableTokenEndpoint("/connect/token")
// Enable the password and the refresh token flows.
.AllowPasswordFlow()
.AllowRefreshTokenFlow();
Use the OAuth2 validation middleware to protect your APIs:
To enable token authentication, reference AspNet.Security.OAuth.Validation 1.0.0-alpha2-final package and add app.UseOAuthValidation() before app.UseMvc(). To make authentication mandatory, simply use the [Authorize] attribute like you'd do with cookies authentication.
Don't hesitate to play with this sample. It doesn't use a mobile app for the client-side part, but you should easily understand how it works.
For more information, you can also read this blog post, written by Mike Rousos for the Microsoft .NET Web Development and Tools blog: Bearer Token Authentication in ASP.NET Core
Ok, Thanks #Pinpoint for pointing me to the right direction.
However here is my Startup.cs configuration:
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
if (env.IsDevelopment())
{
// For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709
builder.AddUserSecrets();
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// 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.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddOpenIddict<ApplicationUser, ApplicationRole, ApplicationDbContext>()
.DisableHttpsRequirement()
.EnableTokenEndpoint("/connect/token")
.AllowPasswordFlow()
.AllowRefreshTokenFlow()
.UseJsonWebTokens();
services.AddMvc();
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}
// 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.UseDatabaseErrorPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseIdentity();
app.UseOpenIddict();
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
RequireHttpsMetadata = false,
Audience = "http://localhost:24624/",
Authority = "http://localhost:24624/"
});
// Add external authentication middleware below. To configure them please see http://go.microsoft.com/fwlink/?LinkID=532715
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
ApplicationDbContext.cs:
public class ApplicationDbContext : OpenIddictDbContext<ApplicationUser, ApplicationRole>
{
public ApplicationDbContext(DbContextOptions options)
: base(options)
{
Database.EnsureCreated();
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
}
ApplicationRole.cs:
public class ApplicationRole : IdentityRole
{
}
ApplicationUser.cs:
public class ApplicationUser : OpenIddictUser
{
}
ServiceController.cs:
[Authorize(ActiveAuthenticationSchemes = OAuthValidationDefaults.AuthenticationScheme)]
[Route("api/service")]
public class ServiceController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
public ServiceController(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
[HttpGet]
[Route("getdata")]
public async Task<IActionResult> GetData()
{
var user = await _userManager.GetUserAsync(User);
if (user == null) return Ok("No user / not logged in");// if Authorize is not applied
return Ok(user);
}
}
The key in here is the ServiceController.cs: [Authorize(ActiveAuthenticationSchemes = OAuthValidationDefaults.AuthenticationScheme)]
#Pinpoint: I didn't use app.UseOAuthValidation() because it was returning 302 and redirect to Account/Login.
So now it works like this:
accessing the http://domain.com, user can register, login, see data, etc.
user can download mobile app, register, login and get data
Implementing the user registration login in the api side is preatty easy and straight forward.
The problem was that using fiddler and issuing a GET to http://domain.com/api/service/getdata was returning a 302 and redirect to Account/Login. If I remove app.UseIdentity(), then if will have returned 401 Unauthorized but user would have not been able to login anymore using the UI http://domain.com. Adding this [Authorize(ActiveAuthenticationSchemes = OAuthValidationDefaults.AuthenticationScheme)] to my ServiceController solved the problem.
#Pinpoint what was the benefit of app.UseOAuthValidation() ?
I'd like to keep using same cookies in MVC and API parts of my app. I know this isn't very secure but still.
Everything works if I create a new MVC project in VS, Web API is set up from Global.asax using GlobalConfiguration.Configure(WebApiConfig.Register).
But as soon as I'm trying to use OWIN to configure Web API I run into a problem where User is always null in my API controllers.
Here's my code from Startup.cs:
var config = new HttpConfiguration();
WebApiConfig.Register(config);
app.UseWebApi(config);
Controllers work, routes too, same WebApiConfig.cs file is used. However the User is null in my API controllers now. What's missing from my instance of HttpConfiguration that is present in GlobalConfiguration.Configuration?
I need to use my own instance of HttpConfiguration instead of using GlobalConfiguration.Configuration because I'm planning to use Autofac and it doesn't work with GlobalConfiguration as mentioned here
EDIT:
My Startup.Auth.cs:
// 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))
}
});
What's missing from my instance of HttpConfiguration that is present
in GlobalConfiguration.Configuration?
GlobalConfiguration.cs Source code from Codeplex
The main difference between when you create a new HttpConfiguration and the one In GlobalConfiguration...
public static class GlobalConfiguration
{
private static Lazy<HttpConfiguration> _configuration = CreateConfiguration();
//...other code removed for brevity
/// <summary>
/// Gets the global <see cref="T:System.Web.Http.HttpConfiguration"/>.
/// </summary>
public static HttpConfiguration Configuration
{
get { return _configuration.Value; }
}
//...other code removed for brevity
private static Lazy<HttpConfiguration> CreateConfiguration()
{
return new Lazy<HttpConfiguration>(() =>
{
HttpConfiguration config = new HttpConfiguration(new HostedHttpRouteCollection(RouteTable.Routes));
ServicesContainer services = config.Services;
Contract.Assert(services != null);
services.Replace(typeof(IAssembliesResolver), new WebHostAssembliesResolver());
services.Replace(typeof(IHttpControllerTypeResolver), new WebHostHttpControllerTypeResolver());
services.Replace(typeof(IHostBufferPolicySelector), new WebHostBufferPolicySelector());
services.Replace(typeof(IExceptionHandler),
new WebHostExceptionHandler(services.GetExceptionHandler()));
return config;
});
}
//...other code removed for brevity
}
Also when looking at how the UseWebAPi extension in
WebApiAppBuilderExtensions.cs
public static IAppBuilder UseWebApi(this IAppBuilder builder, HttpConfiguration configuration)
{
if (builder == null)
{
throw new ArgumentNullException("builder");
}
if (configuration == null)
{
throw new ArgumentNullException("configuration");
}
HttpServer server = new HttpServer(configuration);
try
{
HttpMessageHandlerOptions options = CreateOptions(builder, server, configuration);
return UseMessageHandler(builder, options);
}
catch
{
server.Dispose();
throw;
}
}
...the configuration is wrapped in its own HttpServer which overrides the default one used by GlobalConfiguration.
Looking through the documentation you included, I eventually came across this
For standard IIS hosting, the HttpConfiguration is
GlobalConfiguration.Configuration.
For self hosting, the HttpConfiguration is your
HttpSelfHostConfiguration instance.
For OWIN integration, the HttpConfiguration is the one you create in
your app startup class and pass to the Web API middleware.
With standard IIS hosting, IIS handles user Authentication and Identification which it plugs into the HttpConfiguration and pipeline under the hood for you. When you new up HttpConfiguration your self you don't have the benefits of IIS to manage Authentication for you so your User remains null.
From your post you indicate that you are using more than one instance of HttpConfiguration which looks like you are trying to mix IIS and OWIN.
Looking at this question : OWIN Cookie Authentication
The answer shows that in the WebApi Config the following line was ignoring the cookie.
// Configure Web API to use only bearer token authentication.
// If you don't want the OWIN authentication to flow to your Web API then call
// SuppressDefaultHostAuthentication on your HttpConfiguration.
// This blocks all host level authentication at that point in the pipeline.
config.SuppressDefaultHostAuthentication();
Commenting it out made the cookie based Authentication work.
UPDATE:
You indicated...
Controllers work, routes too, same WebApiConfig.cs file is used.
However the User is null in my API controllers now
Take a look at...
Combining Authentication Filters with Host-Level Authentication
“Host-level authentication” is authentication performed by the host
(such as IIS), before the request reaches the Web API framework.
Often, you may want to to enable host-level authentication for the
rest of your application, but disable it for your Web API controllers.
For example, a typical scenario is to enable Forms Authentication at
the host level, but use token-based authentication for Web API.
To disable host-level authentication inside the Web API pipeline, call
config.SuppressHostPrincipal() in your configuration. This causes
Web API to remove the IPrincipal from any request that enters the
Web API pipeline. Effectively, it "un-authenticates" the request.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.SuppressHostPrincipal();
// Other configuration code not shown...
}
}
If in your scenario you have the following in your web api configuration, it would explain why your User is always null. I suggest you comment it out or remove it all together.
I had exactly this problem when I transferred to OWIN from a WebApi only service. My user was also null even though it was correctly authenticated. In my case I had missed adding the HostAuthenticationFilter in after suppressing the Default Host Authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(YourAuthenticationType));
Check that you have not forgotten your authentication filter here. I've done this successfully using Unity (rather than Autofac) but the principal is exactly the same. In my OWIN startup this order:
ConfigureAuth(app);
WebApiConfig.Register(httpConfiguration);
app.UseWebApi(httpConfiguration);