Hope anyone can help me with the following highly frustrating problem.
I have .NET WebApi which I secured with jwt authentication. In setting up the OAuthAuthorizationServerOptions-object I set the property AllowInsecureHttp for my token end point to false. Running my API locally on IISExpress and testing it with Postman works like a charm. If I set the property to true and ask for a token on my end point it works, if I set it to false I get a 404 as expected. But when I publish the API to my production environment (Windows 2012/IIS8) and set the property to false, I can get a token over both https and http. It just doesn't seem to listen to the property.
I run the API as an application with alias Api under a domain with just one https-binding. I use the following helper base class for my API's:
public class BaseWebApi<TDerivedOAuthProvider> where TDerivedOAuthProvider : BaseOAuthProvider, new()
{
public BaseWebApi(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(DefaultAuthenticationTypes.ExternalBearer));
ConfigureOAuth(app);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
private static void ConfigureOAuth(IAppBuilder app)
{
var allowInsecureHttp = Convert.ToBoolean(ConfigurationManager.AppSettings["jwt::allowInsecureHttp"].ToString());
var issuer = ConfigurationManager.AppSettings["jwt::issuer"].ToString();
var secret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["jwt::secret"].ToString());
var clients = ConfigurationManager.AppSettings["jwt::clients"].ToString().Split(new char[] { ',' });
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = allowInsecureHttp,
AuthenticationType = DefaultAuthenticationTypes.ExternalBearer,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(10),
Provider = new TDerivedOAuthProvider(),
RefreshTokenProvider = new BaseRefreshTokenProvider(),
AccessTokenFormat = new BaseJwtFormat(issuer),
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ExternalBearer,
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = clients,
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
}
});
}
}
Variables allowInsecureHttp, issuer, secret and clients are filled from a configuration file. Setting the value for allowInsecureHttp hardcoded to false or true does not change anything. This base class is then instantiated by my actual API which (next to the actual API functions) also provides a CustomOAuthProvider class handling the actual implementation of the credential checking for this particular API:
[assembly: OwinStartup(typeof(MyCustomAPI.Startup))]
namespace MyCustomAPI.API
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
BaseWebApi<CustomOAuthProvider> webApi = new BaseWebApi<CustomOAuthProvider>(app);
}
}
}
Hope anyone can point me in the right direction. Set aside this issue the API itself is working flawlessly, but I really want to force SSL on my production tokens.
Cheers,
Nyquist
For anyone ever encountering the same issue. Figured it out eventually. Worked like a charm, but the HSTS policy on the domain I configured my virtual application redirected all http Postman traffic automatically to https. Installed the application on a simple domain without SSL and on that domain SSL was perfectly enforced. sigh :)
Related
I appreciate any help on this. I'm currently working on a project where we basically have to convert an older .Net Framework MVC application into an API being utilized by a React front end. We were able to basically convert an MVC controller into an API controller without using ApiController. Now we realized we need to secure the API, and have decided to implement a JWT.
I was able to implement the JWT token creation, and I have that working in my Account Controller which is returning the JWT. The problem I ran into was that I then wanted to implement Authorization on certain endpoints, but had issues with it working on the existing controllers. The documentation at https://github.com/DavidParks8/Owin-Authorization/wiki/Claims-Based-Authorization stated that I should be able to use the authorization on either type of controller, so I don't know what I'm doing wrong.
I thought maybe it was because I was using Controller as opposed to ApiController, so I set up a test controller and created some basic endpoints based on a JWT tutorial I was following. The problem is that when I am attempting to test those endpoints they are not being hit at all and I'm getting a 404.
Here is my configuration:
{
string secretKey = WebConfigurationManager.AppSettings["SecretKey"];
SymmetricSecurityKey _signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey));
// 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 JWT Authentication:
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidIssuer = WebConfigurationManager.AppSettings["Audience"],
ValidAudience = WebConfigurationManager.AppSettings["Audience"],
IssuerSigningKey = _signingKey,
ClockSkew = TimeSpan.Zero,
ValidateLifetime = true,
RequireExpirationTime = false
},
});
//AuthorizationOptions options = new AuthorizationOptions();
app.UseAuthorization(options =>
{
options.AddPolicy("Principal", policy => policy.RequireClaim(Constants.JwtClaimIdentifiers.Rol, "Principal"));
options.AddPolicy("Employee", policy => policy.RequireClaim(Constants.JwtClaimIdentifiers.Rol, Constants.JwtClaims.Employee));
options.AddPolicy("Warranty", policy => policy.RequireClaim(Constants.JwtClaimIdentifiers.Rol, Constants.JwtClaims.Principal));
options.AddPolicy("Admin", policy => policy.RequireClaim(Constants.JwtClaimIdentifiers.Rol, Constants.JwtClaims.Principal));
});
This was based on documentation and tutorials I went over, and is using Microsoft.Owin.Security.Authorization and Microsoft.Owin.Security.Jwt
Here is my WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
var json = config.Formatters.JsonFormatter;
json.SupportedMediaTypes
.Add(new MediaTypeHeaderValue("text/html"));
json.SerializerSettings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
// Web API routes
config.MapHttpAttributeRoutes();
config.EnableCors();
//config.MessageHandlers.Add(new TokenValidationHandler());
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//var jsonpFormatter = new JsonpMediaTypeFormatter(config.Formatters.JsonFormatter);
//config.Formatters.Insert(0, jsonpFormatter);
}
}
And here is my test controller:
public class TestController : ApiController
{
[HttpGet]
[AllowAnonymous]
[Route("GetToken")]
public Object GetToken()
{
string key = "my_secret_key_12345"; //Secret key which will be used later during validation
var issuer = "example.com"; //normally this will be your site URL
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
//Create a List of Claims, Keep claims name short
var permClaims = new List<Claim>();
permClaims.Add(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()));
permClaims.Add(new Claim("valid", "1"));
permClaims.Add(new Claim("userid", "1"));
permClaims.Add(new Claim("name", "bilal"));
//Create Security Token object by giving required parameters
var token = new JwtSecurityToken(issuer, //Issuer
issuer, //Audience
permClaims,
expires: DateTime.Now.AddDays(1),
signingCredentials: credentials);
var jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
return new { data = jwtToken };
}
[HttpGet]
[AllowAnonymous]
[Route("GetName1")]
public string GetName1()
{
if (User.Identity.IsAuthenticated)
{
var identity = User.Identity as ClaimsIdentity;
if (identity != null)
{
IEnumerable<Claim> claims = identity.Claims;
}
return "Valid";
}
else
{
return "Invalid";
}
}
[HttpPost]
[Route("GetName2")]
public Object GetName2()
{
if (User.Identity is ClaimsIdentity identity)
{
IEnumerable<Claim> claims = identity.Claims;
var name = claims.Where(p => p.Type == "name").FirstOrDefault()?.Value;
return new
{
data = name
};
}
return null;
}
}
So when I try to call http://localhost:29523/api/Test/GetToken, I'm getting the 404 error. I'm using Postman, and I can hit endpoints in another controller, but it's an MVC controller, and it's not following API routing. For example, /registration/GetAccountManagementInfo?userGuid=a182235e-c71d-47a5-b31d-f556288b3c3f works and I'm able to get the response.
To summarize, the reason I'm testing with this ApiController is because if I can make this work, and get the Authorization to work on ApiController then I'm going to convert my "registration" controller into an ApiController, since I have had trouble getting that controller to use the authorization middleware that I set up.
Anyway, I'm sure there is other information I could provide, but I'm at my wits end at this point trying to figure out how to troubleshoot this further.
I know that it's trying to reach the controller because if I add a constructor it's hitting the breakpoint at the constructor, but just can't find the route from there. I don't see what's wrong with my setup but I feel like there have to be some wires crossed somewhere with this set up. This is an application I inherited. I wish I could start in a fresh project with .Net core but we just don't have the time for that right now.
Anyways, sorry for the length of this post, I'm really hoping someone can give me some direction. I've looked through countless other posts getting to this point and I haven't found anything that has helped me figure this out so far.
Did you try this? It doesn't seem you have to put Test in address. In your case it should be without "Test".
http://localhost:29523/api/GetToken
I have an existing MVC project that uses FormsAuthentication for its authentication.
I need to incorporate the option of logging in with an OpenID IDP in addition to the regular login page already available.
The problem I'm having is challenging the IDP on demand and setting the authentication cookie once the claims are received, I can't find the reason why the cookie is not sticking. The flow seems to be working fine, and I can see the claims in the AuthorizationCodeReceived callback.
Here's the Startup.Auth.cs code:
var notificationHandlers = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = (context) =>
{
string username = context.AuthenticationTicket.Identity.FindFirst("preferred_username").Value;
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1, username, DateTime.Now, DateTime.Now.AddMinutes(60), true, "");
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
context.Response.Cookies.Append(FormsAuthentication.FormsCookieName, encryptedTicket);
return Task.FromResult(0);
},
RedirectToIdentityProvider = (context) =>
{
if (context.OwinContext.Request.Path.Value != "/Account/SignInWithOpenId")
{
context.OwinContext.Response.Redirect("/Account/Login");
context.HandleResponse();
}
return Task.FromResult(0);
}
};
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
SignInAsAuthenticationType = "Cookies",
Authority = "xxxxxxxxx",
ClientId = "MyClient",
ClientSecret = "xxxxxxxx",
RedirectUri = "http://localhost:52389/",
PostLogoutRedirectUri = "http://localhost:52389/",
ResponseType = "code id_token",
Scope = "openid profile email roles",
UseTokenLifetime = false,
TokenValidationParameters = new TokenValidationParameters()
{
NameClaimType = "preferred_username",
RoleClaimType = "role"
},
Notifications = notificationHandlers
});
app.SetDefaultSignInAsAuthenticationType("Cookies");
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = "Cookies",
AuthenticationMode = AuthenticationMode.Passive,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider()
});
app.UseStageMarker(PipelineStage.Authenticate);
And here's the AccountController SignInWithOpenId method:
public ActionResult SignInWithOpenId()
{
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(OpenIdConnectAuthenticationDefaults.AuthenticationType);
// If I don't have this line, reponse redirects to the forms authentication login... so maybe something is wrong here?
return new HttpUnauthorizedResult("IDP");
}
else
{
return RedirectToAction("Index", "Default");
}
}
Any pointers would be greatly appreciated. Thank you.
This is the exact thing I'm trying to do at the moment. I will let you know if I find anything useful.
Update:
I ended up disabling Forms Authentication in the MVC web app. I was doing a proof of concept so it wasn't a hard requirement. I know this was not really what you were getting at. I successfully used my IdP to login and redirect back to the web app. Where the proof of concept ended was the HttpContext.User object was needed to be populated.
I was able to get this, or at least an equivalent, working in .NET 4.7. My use case is that most subscribers are being upgraded to log in via Azure AD B2C, but we have public PCs that we want to authenticate with a manual claim via an obscured URL.
I'm using Microsoft.Owin.Security.OpenIDConnect and related packages, and the Owin startup is standard, although I will point out this line:
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
I had to disable Forms authentication entirely; I could not get this working when anything other than Anonymous Authentication was enabled in IIS.
The core of the solution was actually an example I found here: How to use OWIN forms authentication without aspnet identity
/* URL validated, add authenticated claim */
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "PublicPC"),
new Claim(ClaimTypes.Email, "PublicPC#example.org")
};
var id = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationType);
var ctx = HttpContext.Current.GetOwinContext();
var authenticationManager = ctx.Authentication;
authenticationManager.SignIn(id);
But critically, I needed to specify CookieAuthenticationDefaults.AuthenticationType, which is what I'm using in the Owin startup.
solved by adding these code to Global.asax:
protected void Application_BeginRequest()
{
Context.Response.SuppressFormsAuthenticationRedirect = true;
}
according to Prevent ASP.NET from redirecting to login.aspx
I am trying to configure Identity 4 server to work with my API project. At this moment I can request token but I need to add user name and role to payload. I tried with IProfileService but no action was performed. How can I obtain this information from windows authentication? Here is my configuration:
launchSettings.json
"iisSettings": {
"windowsAuthentication": true,
"anonymousAuthentication": false
Program.cs
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseKestrel()
.UseIISIntegration()
.UseStartup<Startup>();
Startup.cs
services.Configure<IISOptions>(iis =>
{
iis.AutomaticAuthentication = true;
});
var builder = services.AddIdentityServer()
.AddInMemoryIdentityResources(IdentityResourcesConfig.Get())
.AddInMemoryApiResources(ApiResourcesConfig.Get())
.AddInMemoryClients(ClientsConfig.Get());
ClientsConfig.cs
return new Client[]
{
new Client
{
ClientId = "XYC",
AllowedGrantTypes = GrantTypes.ClientCredentials,
AllowedScopes = { "XYC" },
RequireClientSecret = false,
AlwaysIncludeUserClaimsInIdToken = true
}
};
I only worked with normal authentication but the classes that are creating and controll the way the claims are shared to other applications should be the same.
You probably just need to add the Claims to the API ressource because by default the claims used by the client will be not inlcuded into the Access Token also given to the client to request an API.
public static IEnumerable<ApiResource> GetApis()
{
return new ApiResource[]
{
new ApiResource("MyApi", "This is my Api name", new List<string> {
"mynameclaimclaimname",
}),
the claim name you add in there is Name of claim.
If this is not working it would be helpful to give us further information. How are the API Ressources configured ( IdentityServer side and client side) ?
Or do you try to configure an API as Client ?
The first point is in IdentityServer, Windows authentication is an external provider (as opposed to the IS native authentication cookie). Windows authentication is triggered by using the ChallengeAsync API on the HttpContext using the scheme Windows.You can click
here for details .
Another point is you are using client credential flow , which is wrong in your scenario . Client credential flow use app's identity , there is no user in it .
I am having ASP.Net webapi which is hosted on Azure. Actually I am having two different subscription
For Azure Active Directory - say its demo.onmicrosoft.com
For hosting webapi. - say its hosted on abc.azurewebsites.net
My api which is hosted on abc.azurewebsites.net is registered on demo.onmicrosoft.com. My client app is a service app which is authenticating against a user that resides in demo.onmicrosoft.com. Authentication is basic authentication by passing user credentials and recieveing AccessToken from Azure Active Directory. After recieving token from demo.onmicrosoft.com I am calling api from abc.azurewebsites.net. Like this:
https://abc.azurewebsites.net/api/some/queryapi
Now, In my controller if I use
[Authorize]
public class SomeController:ApiController
{
//my code
}
I am getting uauthorized access. and if I remove that attribute from controller it works fine.
Can you help me out.Even after app registration & authentication why I am getting so. Is that because of two different subscription or something else.
Update
I am sending token to my webapi this way
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
}
IMO, since my token is from azure ad of some other subscription so its not getting recognized by my webapi.
More Update - Startup.cs
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters {
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
},
});
}
Web.config
<add key="ida:Tenant" value="demo.onmicrosoft.com" />
<add key="ida:Audience" value="https://demo.onmicrosoft.com/4e4xxxx5-5xx1-4355-8xxc-705xxxx163" />
<add key="ida:ClientID" value="d0xxxxa-2xxx6-4xx-9e58-07xxxxxxxx1" />
It is relative to how you protect the web API instead of Azure subscription. For example, here is a piece of code which used to protect web API using Azure AD in the .net core:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// Add the console logger.
loggerFactory.AddConsole(LogLevel.Debug);
// Configure the app to use Jwt Bearer Authentication
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Authority = String.Format(Configuration["AzureAd:AadInstance"], Configuration["AzureAD:Tenant"]),
Audience = Configuration["AzureAd:Audience"],
Events = new JwtBearerEvents
{
OnTokenValidated = tokenValidated ,
OnAuthenticationFailed= AuthenticationFailed
}
});
}
This web API will verify the signature of the token to ensure the token is issued from Azure AD. Then it will check the aud claim in the access token. If the aud claim also matched as we config in above code the we can call the web API successfully.
1). Make sure Azure Authentication setting off.
2). In StartupAuth.cs
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
// Note: Remove the following line before you deploy to production:
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
follow this article: https://www.asp.net/web-api/overview/security/individual-accounts-in-web-api
I'm trying to implement JWT authorization in a project. However in order to successfully get the token I have to pass client_id from AngularJS frontend to ASP.NET Web API backend and as far as I know it is not secure at all. So could someone please give me a hint about what should I be doing in my situation.
On JS side -
var data = 'grant_type=password&username='
+ loginData.Email + '&password=' + loginData.Password + '&client_id=' + client_id;
$http.post('/oauth2/token', data); //Code omitted
I'm using this guide for creating a Jwt authorization, for the most part. Except I have an app on one domain, so here is what my Startup.cs looks like -
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
ConfigureOAuth(app);
ConfigureValidationOAuth(app);
}
private static void ConfigureOAuth(IAppBuilder app)
{
var oAuthServerOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth2/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
Provider = new CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat(ConfigurationManager.AppSettings["owin:issuer"])
};
app.UseOAuthAuthorizationServer(oAuthServerOptions);
}
private static void ConfigureValidationOAuth(IAppBuilder app)
{
var issuer = ConfigurationManager.AppSettings["owin:issuer"];
var audience = ConfigurationManager.AppSettings["owin:audience"];
var secret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["owin:secret"]);
//Api controllers with [Authorize] attribute will be validated with Jwt
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] {audience},
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
}
});
}
JWT authentication and authorization should work like so:
pass username and pass to server
server checks the user data and generates the JWT token which should be in this format: (check out JWT.io for more info)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
the JWT token should be stored client side in a local storage
to make your life easier you should create an angular HTTP request interceptor that automatically appends the saved JWT token to the request headers. Something like this:
myApp.factory('jwt-interceptor', ['$q', '$window', function($q, $window) {
return {
request: function(request) {
request.headers['Authorization'] = 'Bearer ' + $window.localStorage.token;
return request;
},
responseError: function(response) {
return $q.reject(response);
}
};
}]).config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('jwt-interceptor');
}]);
server should read the header param named Authorization, decompile the token and check if the payload:
a. was decompiled correctly and the payload is intact
b. check if the expiry timestamp in the payload is bigger then the current timestamp
c. other user permission related checks (if required)