.Net Core / Identity Server - Is it possible to AllowAnonymous but only from my client? - asp.net-core

I have a REST API and an IdentityServer set up. I would like to be able to display items in my client from the API without having to sign in. However, I would also like to protect my API from external clients that don't belong to me. Is it possible to AllowAnonymous but only from my client?
[HttpGet]
[AllowAnonymous]
public List<Item> GetItems()
{
return new List<Item> { "item1", "item2" };
}
Edit w/ Solution
As mentioned by Tore Nestenius, I changed the grant types from Code to CodeAndClientCredentials and added the Authorize attribute to my controller so that only my client can access it.
Controller:
[HttpGet]
[Authorize]
public List<Item> GetItems()
{
return new List<Item> { "item1", "item2" };
Identity Server 4 Config File:
public static IEnumerable<Client> Clients =>
new Client[]
{
new Client
{
ClientId = "postman-api",
AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
}
};
}

CORS only works for requests from browsers, if a non browser application makes a request, then CORS will not be involved.
if you use [AllowAnonymous], then any client can access that API endpoint. Either you create separate client for the general things, perhaps using the Client Credentials flow, so that the client can authenticate, get its own token without any user involved.

Turns out this is handled by CORS.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddDefaultPolicy(
builder => builder
.WithOrigins("yourURL")
.AllowAnyMethod());
})
}

Related

Is there a better way of JWT Web Token combined with Windows Auth. for building auth service in an ASP.NET Core project?

The reason behind my question is that, there is a beginner developer team at a company, starting to create a new business project after finishing some vital courses for web applications.
The aim is to have a Web Application within the company's intranet in the following form:
On Angular SPA frontend with ASP.NET Core WebAPI, using Entity Framework Core with a Microsoft SQL Server database running on Windows Server.
The current authentication method of course is Windows Authentication.
In order to create proper auth services, it was suggested to use JWT Web Token, however it is hard to tell whether there is a better approach for using authentication by combining them on the above mentioned Web Application.
As we are lacking of experience, a review of any familiars' would be highly appreciated in this matter!
The current authentication method of course is Windows Authentication.
In order to create proper auth services, it was suggested to use JWT
Web Token.
As you may know JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object.Therefore, JWT creates a JSON web token and encodes, sterilizes, and adds a signature with a secret key that cannot be tampered with; Thus, it would ensure your application security well.
It is hard to tell whether there is a better approach for using
authentication by combining them on the above mentioned Web
Application.
Depending on your current application eco-system you could use Jwt without any concern as you have SPAs and other application running on intranet. While, implementing jwt it would allow you to ensure your authentication regardless of any platform. For instance, It could be windows app, SPA or any cross platform app. You can authenticate all the platfroms using this infrastructure.
As we are lacking of experience, a review of any familiars' would be
highly appreciated in this matter!
Considering your scenario, here is the implementaion steps, you could follow. You always can customize it based on your requirement. Altough, I am share you the basic steps which might assist you.
Note:
Following implementation, can be used either in any internal(intranet) or public web application(internet app) in any platforms.
Implementaion Guideline:
appsettings.json:
"Jwt": {
"Key": "Set_Your_SecretKey",
"Issuer": "YourApplication_URL"
}
Jwt Token View Model:
public class LoginViewModel
{
[Required]
[EmailAddress]
public string Email { get; set; } = string.Empty;
[Required]
[DataType(DataType.Password)]
public string Password { get; set; } = string.Empty;
}
Jwt Token Interface:
public interface IAuthenticationRepository
{
Task<TokenViewModel> AuthenticateLogin(LoginViewModel loginInfo);
}
Repository Implementation:
public class AuthenticationRepository : IAuthenticationRepository
{
private readonly ApplicationDbContext _dbContext;
private readonly IMapper _mapper;
private readonly IConfiguration _config;
public AuthenticationRepository(ApplicationDbContext dbContext, IMapper mapper, IConfiguration config)
{
_dbContext = dbContext;
_mapper = mapper;
_config = config;
}
public async Task<TokenViewModel> AuthenticateLogin(LoginViewModel loginInfo)
{
try
{
var isAuthenticate = await _dbContext.Users.FirstOrDefaultAsync(u => u.UserEmail == loginInfo.Email && u.Password == loginInfo.Password);
var tokenViewModel = new TokenViewModel();
if (isAuthenticate != null)
{
var getToken = GenerateJSONWebToken(loginInfo);
tokenViewModel = _mapper.Map<TokenViewModel>(isAuthenticate);
tokenViewModel.Token = getToken;
}
return tokenViewModel;
}
catch (Exception ex)
{
throw;
}
}
private string GenerateJSONWebToken(LoginViewModel userInfo)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, userInfo.Email),
new Claim(JwtRegisteredClaimNames.Email, userInfo.Password),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var token = new JwtSecurityToken(_config["Jwt:Issuer"],
_config["Jwt:Issuer"],
claims,
expires: DateTime.Now.AddMinutes(120),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
Response Model I Have Used:
public class ResponseViewModel
{
public string output { get; set; }
public string msg { get; set; }
public object apiResponse { get; set; }
}
Auth Controller:
[Route("api/Authentication")]
[ApiController]
public class AuthenticationController : ControllerBase
{
private readonly IAuthenticationRepository _authenticationService;
public AuthenticationController(IAuthenticationRepository authenticationService)
{
this._authenticationService = authenticationService;
}
[AllowAnonymous]
[Route("login")]
[HttpPost]
public async Task<IActionResult> LoginAsync([FromBody] LoginViewModel loginInfo)
{
IActionResult response = Unauthorized();
var user = await _authenticationService.AuthenticateLogin(loginInfo);
if (user.Token != null)
{
response = Ok(new ResponseViewModel { output = "success", msg = "Login Successfully", apiResponse = user });
}
return response;
}
Authenticate Your Access:
Once you have successfully generate jwt auth token, now you can pass that as Bearer token for any authorization and to restrict access you can use [Authorize] before any resource where you wants to restrict access.
Output:
Note: If you would like to know more details on jwt token you could check our official document here

allow anonymous access if request from the specific URL or the same site asp.net core 3

I have web APIs hosted in a web application and consumed by the same site frontend by ajax requests. I need to allow anonymous access to these APIs if the request from the same web application frontend APIs host in, but if the request from an external requester its must be authorized. I use identity server 4 Bearer to secure the APIs and asp.net core 3.
You have to do two things:
Add the default (non-whitelisted) authentication as usual
Add a custom authorization policy that check the client IP
I assume you got number 1 covered. Here's how you handle number 2:
Add an authorization policy, and make it the default:
services.AddAuthorization(options =>
{
options.AddPolicy("AllowedIpPolicy", config =>
{
config.AddRequirements(new AllowedIpRequirement());
});
options.DefaultPolicy = options.GetPolicy("AllowedIpPolicy");
});
Add an authorization requirement AllowedIpRequirement, which is just an empty class:
public class AllowedIpRequirement : IAuthorizationRequirement { }
Create a handler for this requirement:
public class AllowedIpRequirementHandler : AuthorizationHandler<AllowedIpRequirement>
{
private readonly IHttpContextAccessor _contextAccessor;
public AllowedIpRequirementHandler(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
AllowedIpRequirement requirement)
{
var httpContext = _contextAccessor.HttpContext;
if (IsAllowedIp(httpContext.Connection.RemoteIpAddress) ||
context.User.Identity.IsAuthenticated)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
private bool IsAllowedIp(IPAddress connectionRemoteIpAddress)
{
// ...check if allowed ip...
}
}
And finally register the handler and the required IHttpContextAccessor service:
services.AddSingleton<IAuthorizationHandler, AllowedIpRequirementHandler>();
services.AddHttpContextAccessor();

Authorise the localhost in ASP.NET Core

I am newbie in ASP.NET Core, and I have a controller I need to authorise it only on my machine, for the test purposes, however, deny on other...
I have the following config:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.DateFormatString= "yyyy-MM-ddTHH:mm:ssZ";
});
services.AddAuthentication("Cookie")
.AddScheme<CookieAuthenticationOptions, CookieAuthenticationHandler>("Cookie", null);
services.AddLogging(builder => { builder.AddSerilog(dispose: true); });
And on the test controlled I enabled the [Authorise] attrubute
[Authorize]
public class OrderController : Controller
Is there a way to allow my local machine to be autorised to acces the controller's actions? Something like [Authorize(Allow=localhost)]
You can create an action filter like so:
public class LocalhostAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var ip = context.HttpContext.Connection.RemoteIpAddress;
if (!IPAddress.IsLoopback(ip)) {
context.Result = new UnauthorizedResult();
return;
}
base.OnActionExecuting(context);
}
}
And then use the tag Localhost:
//[Authorize]
[Localhost]
public class OrderController : Controller
I believe this will work, restricting the access to the machine where it's executed.
This is more whitelisting than authorization. Authorization means checking whether a user has permission to do something. To do that, the user must be identified first, ie authenticated.
The article Client IP Safelist in the docs shows how you can implement IP safelists through middleware, an action filter or a Razor Pages filter.
App-wide Middleware
The middleware option applies to the entire application. The sample code retrieves the request's endpoint IP, checks it against a list of safe IDs and allows the call to proceed only if it comes from a "safe" list. Otherwise it returns a predetermined error code, in this case 401:
public async Task Invoke(HttpContext context)
{
if (context.Request.Method != "GET")
{
var remoteIp = context.Connection.RemoteIpAddress;
_logger.LogDebug("Request from Remote IP address: {RemoteIp}", remoteIp);
string[] ip = _adminSafeList.Split(';');
var bytes = remoteIp.GetAddressBytes();
var badIp = true;
foreach (var address in ip)
{
var testIp = IPAddress.Parse(address);
if(testIp.GetAddressBytes().SequenceEqual(bytes))
{
badIp = false;
break;
}
}
if(badIp)
{
_logger.LogInformation(
"Forbidden Request from Remote IP address: {RemoteIp}", remoteIp);
context.Response.StatusCode = 401;
return;
}
}
await _next.Invoke(context);
}
The article shows registering it before UseMvc() which means the request will be rejected before reaching the MVC middleware :
app.UseMiddleware<AdminSafeListMiddleware>(Configuration["AdminSafeList"]);
app.UseMvc();
This way we don't waste CPU time routing and processing a request that's going to be rejected anyway. The middleware option is a good choice for implementing a blacklist too.
Action Filter
The filtering code is essentially the same, this time defined in a class derived from ActionFilterAttribute. The filter is defined as a scoped service :
services.AddScoped<ClientIpCheckFilter>();
services.AddMvc(options =>
{
options.Filters.Add
(new ClientIpCheckPageFilter
(_loggerFactory, Configuration));
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
In this case the request will reach the MVC infrastructure before it's accepted or rejected.
Razor Pages Filter
The code is once more the same, this time deriving from IPageFilter

How to Consume/Validate Token Issued by AspNet.Security.OpenIdConnect.Server (RC1)?

I have followed everything I know from posts regarding how to implement AspNet.Security.OpenIdConnect.Server.
Pinpoint, do you hear me? ;)
I've managed to separate token issuing and token consumption. I won't show the "auth server side" because I think that part is all set, but I'll show how I built the authentication ticket inside my custom AuthorizationProvider:
public sealed class AuthorizationProvider : OpenIdConnectServerProvider
{
// The other overrides are not show. I've relaxed them to always validate.
public override async Task GrantResourceOwnerCredentials(GrantResourceOwnerCredentialsContext context)
{
// I'm using Microsoft.AspNet.Identity to validate user/password.
// So, let's say that I already have MyUser user from
//UserManager<MyUser> UM:
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
//identity.AddClaims(await UM.GetClaimsAsync(user));
identity.AddClaim(ClaimTypes.Name, user.UserName);
(await UM.GetRolesAsync(user)).ToList().ForEach(role => {
identity.AddClaim(ClaimTypes.Role, role);
});
var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity),
new AuthenticationProperties(),
context.Options.AuthenticationScheme);
// Some new stuff, per my latest research
ticket.SetResources(new[] { "my_resource_server" });
ticket.SetAudiences(new[] { "my_resource_server" });
ticket.SetScopes(new[] { "defaultscope" });
context.Validated(ticket);
}
}
And startup at the auth server:
using System;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.Data.Entity;
using Microsoft.Extensions.DependencyInjection;
using MyAuthServer.Providers;
namespace My.AuthServer
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication();
services.AddCaching();
services.AddMvc();
string connectionString = "there is actually one";
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<MyDbContext>(options => {
options.UseSqlServer(connectionString).UseRowNumberForPaging();
});
services.AddIdentity<User, Role>()
.AddEntityFrameworkStores<MyDbContext>().AddDefaultTokenProviders();
}
public void Configure(IApplicationBuilder app)
{
app.UseIISPlatformHandler();
app.UseOpenIdConnectServer(options => {
options.ApplicationCanDisplayErrors = true;
options.AllowInsecureHttp = true;
options.Provider = new AuthorizationProvider();
options.TokenEndpointPath = "/token";
options.AccessTokenLifetime = new TimeSpan(1, 0, 0, 0);
options.Issuer = new Uri("http://localhost:60556/");
});
app.UseMvc();
app.UseWelcomePage();
}
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
}
}
Sure enough, when I have this HTTP request, I do get an access token, but I'm not sure if that access token has all the data that the resource server expects.
POST /token HTTP/1.1
Host: localhost:60556
Content-Type: application/x-www-form-urlencoded
username=admin&password=pw&grant_type=password
Now, At the resource server side, I'm using JWT Bearer Authentication. On startup, I've got:
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.Data.Entity;
using Microsoft.Extensions.DependencyInjection;
namespace MyResourceServer
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
string connectionString = "there is actually one";
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<MyDbContext>(options => {
options.UseSqlServer(connectionString).UseRowNumberForPaging();
});
services.AddIdentity<User, Role>()
.AddEntityFrameworkStores<MyDbContext>().AddDefaultTokenProviders();
}
public void Configure(IApplicationBuilder app)
{
app.UseIISPlatformHandler();
app.UseMvc();
app.UseWelcomePage();
app.UseJwtBearerAuthentication(options => {
options.Audience = "my_resource_server";
options.Authority = "http://localhost:60556/";
options.AutomaticAuthenticate = true;
options.RequireHttpsMetadata = false;
});
}
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
}
}
When I make this HTTP request to the resource server, I get a 401 Unauthorized:
GET /api/user/myroles HTTP/1.1
Host: localhost:64539
Authorization: Bearer eyJhbGciOiJS...
Content-Type: application/json;charset=utf-8
The controller who has a route to /api/user/myroles is decorated with a plain [Authorize] with no parameters.
I feel like I'm missing something in both auth and resource servers, but don't know what they are.
The other questions that ask "how to validate token issued by AspNet.Security.OpenIdConnect.Server" don't have an answer. I would appreciate some help in this.
Also, I've noticed that there is OAuth Introspection commented out in the sample provider, and have read somewhere that Jwt is not going to be supported soon. I can't find the dependency that gives me the OAuth Instrospection.
UPDATE I've included both of my startup.cs, from each of auth and resource servers. Could there be anything wrong that would cause the resource server to always return a 401 for every request?
One thing I didn't really touch throughout this whole endeavor is signing. It seems to generate a signature for the JWT at the auth server, but the resource server (I guess) doesn't know the signing keys. Back in the OWIN projects, I had to create a machine key and put on the two servers.
Edit: the order of your middleware instances is not correct: the JWT bearer middleware must be registered before MVC:
app.UseIISPlatformHandler();
app.UseJwtBearerAuthentication(options => {
options.Audience = "my_resource_server";
options.Authority = "http://localhost:60556/";
options.AutomaticAuthenticate = true;
options.RequireHttpsMetadata = false;
});
app.UseMvc();
app.UseWelcomePage();
Sure enough, when I have this HTTP request, I do get an access token, but I'm not sure if that access token has all the data that the resource server expects.
Your authorization server and resource server configuration look fine, but you're not setting the "destination" when adding your claims (don't forget that to avoid leaking confidential data, AspNet.Security.OpenIdConnect.Server refuses to serialize the claims that don't explicitly specify a destination):
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(ClaimTypes.Name, user.UserName, destination: "id_token token");
(await UM.GetRolesAsync(user)).ToList().ForEach(role => {
identity.AddClaim(ClaimTypes.Role, role, destination: "id_token token");
});
Also, I've noticed that there is OAuth Introspection commented out in the sample provider, and have read somewhere that Jwt is not going to be supported soon. I can't find the dependency that gives me the OAuth Instrospection.
Starting with the next beta (ASOS beta5, not yet on NuGet.org when writing this answer), we'll stop using JWT as the default format for access tokens, but of course, JWT will still be supported OTB.
Tokens now being opaque by default, you'll have to use either the new validation middleware (inspired from Katana's OAuthBearerAuthenticationMiddleware) or the new standard introspection middleware, that implements the OAuth2 introspection RFC:
app.UseOAuthValidation();
// Alternatively, you can also use the introspection middleware.
// Using it is recommended if your resource server is in a
// different application/separated from the authorization server.
//
// app.UseOAuthIntrospection(options => {
// options.AutomaticAuthenticate = true;
// options.AutomaticChallenge = true;
// options.Authority = "http://localhost:54540/";
// options.Audience = "resource_server";
// options.ClientId = "resource_server";
// options.ClientSecret = "875sqd4s5d748z78z7ds1ff8zz8814ff88ed8ea4z4zzd";
// });
You can find more information about these 2 middleware here: https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/issues/185

How are bearer tokens stored server-side in Web API 2?

I am setting up bearer token authentication in Web API 2, and I don't understand how (or where) the bearer token is being stored server-side. Here is the relevant code:
Startup:
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static Func<UserManager<IdentityUser>> UserManagerFactory { get; set; }
public static string PublicClientId { get; private set; }
static Startup()
{
PublicClientId = "self";
UserManagerFactory = () => new UserManager<IdentityUser>(new UserStore<IdentityUser>());
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
}
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions());
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseOAuthBearerTokens(OAuthOptions);
}
}
WebApiConfig:
public class WebApiConfig
{
public static void ConfigureWebApi()
{
Register(GlobalConfiguration.Configuration);
}
public static void Register(HttpConfiguration http)
{
AuthUtil.ConfigureWebApiToUseOnlyBearerTokenAuthentication(http);
http.Routes.MapHttpRoute("ActionApi", "api/{controller}/{action}", new {action = Actions.Default});
}
}
AuthUtil:
public class AuthUtil
{
public static string Token(string email)
{
var identity = new ClaimsIdentity(Startup.OAuthOptions.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, email));
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
var currentUtc = new SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromMinutes(30));
var token = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
return token;
}
public static void ConfigureWebApiToUseOnlyBearerTokenAuthentication(HttpConfiguration http)
{
http.SuppressDefaultHostAuthentication();
http.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
}
}
LoginController:
public class LoginController : ApiController
{
...
public HttpResponseMessage Post([FromBody] LoginJson loginJson)
{
HttpResponseMessage loginResponse;
if (/* is valid login */)
{
var accessToken = AuthUtil.Token(loginJson.email);
loginResponse = /* HTTP response including accessToken */;
}
else
{
loginResponse = /* HTTP response with error */;
}
return loginResponse;
}
}
Using the above code, I'm able to login and store the bearer token client-side in a cookie, and then make calls to controllers marked with [Authorize] and it lets me in.
My questions are:
Where / how is the bearer token being stored server-side? It seems like this is hapenning through one of the OWIN calls but I can't tell where.
Is it possible to persist the bearer tokens to a database server-side so that they can remain in place after a Web API server restart?
If the answer to #2 is no, is there anyway for a client to maintain its bearer token and re-use it even after the Web API goes down and comes back up? While this may be rare in Production, it can happen quite often doing local testing.
They're not stored server side -- they're issued to the client and the client presents them on each call. They're verified because they're signed by the owin host's protection key. In SystemWeb hosting, that protection key is the machineKey setting from web.config.
That's unnecessary, as long as the protection key the owin host uses doesn't change across server restarts.
A client can hold onto a token for as long as the token is valid.
For those who are looking for how to set web.config, here is a sample
<system.web>
<machineKey validation="HMACSHA256" validationKey="64-hex"
decryption="AES" decryptionKey="another-64-hex"/>
</system.web>
You need both validationKey and decriptionkey to make it work.
And here is how to generate keys
https://msdn.microsoft.com/en-us/library/ms998288.aspx
To add to this, the token can be persisted server side using the SessionStore property of of CookieAuthenticationOptions. I wouldn't advocate doing this but it's there if your tokens become excessively large.
This is an IAuthenticationSessionStore so you could implement your own storage medium.
By default the token is not stored by the server. Only your client has it and is sending it through the authorization header to the server.
If you used the default template provided by Visual Studio, in the Startup ConfigureAuth method the following IAppBuilder extension is called: app.UseOAuthBearerTokens(OAuthOptions).