ASP.Net WebApi responding unauthorized acess - c#

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

Related

Azure AD Authentication with custom roles maintained in SQL Server in ASP.NET MVC 5.0

First up, I am NOT going to be maintaining roles (claims) from within Azure AD, as I have to maintain it within SQL Server. So, for Authentication, I am using Azure AD. Once authenticated, I query my claims tables (aspnetmembership) and add it to the identity.
Right now, the below code seems to be working fine. But I don't feel confident at all due to these questions I have, as I just don't know if I have coded it right.
Here's my code and here are my questions:
Like we do with Forms auth, once authenticated, am I supposed to set the Thread.Currentprincipal, as well as Context.User even with Azure AD authentication or does this line of code automatically do that for me (I sign in once azure ad authenticates fine)
HttpContext.Current.GetOwinContext().Authentication.SignIn(identity);
If yes to the above question, I am really confused as to how I must sequence the above Signin line of code with setting the Principal (as well as Context.User) with the Azure AD authenticated identity?
I never knew this but does the [Authorize] attribute in MVC 5.0 automatically do the call to check if the request is 'authenticated' as well?
How do I access the custom claims that I added in Startup, within my controllers?
Can you please explain how I need to be handling the cookies with AZure AD authentication?
Thanks in advance!
Here's my code:
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
//app.UseCookieAuthentication(new CookieAuthenticationOptions
//{
// CookieDomain = "localhost",
// SlidingExpiration = true,
// ExpireTimeSpan = TimeSpan.FromHours(2)
//});
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Sets the ClientId, authority, RedirectUri as obtained from web.config
ClientId = clientId,
Authority = authority,
RedirectUri = null,
// PostLogoutRedirectUri is the page that users will be redirected to after sign-out. In this case, it is using the home page
PostLogoutRedirectUri = redirectUri,
Scope = OpenIdConnectScope.OpenIdProfile,
// ResponseType is set to request the code id_token - which contains basic information about the signed-in user
ResponseType = OpenIdConnectResponseType.CodeIdToken,
// ValidateIssuer set to false to allow personal and work accounts from any organization to sign in to your application
// To only allow users from a single organizations, set ValidateIssuer to true and 'tenant' setting in web.config to the tenant name
// To allow users from only a list of specific organizations, set ValidateIssuer to true and use ValidIssuers parameter
TokenValidationParameters = new TokenValidationParameters()
{
//NameClaimType = "preferred_username",
ValidateIssuer = false // TODO: SET THIS TO TRUE EVENTUALLY.
},
// OpenIdConnectAuthenticationNotifications configures OWIN to send notification of failed authentications to OnAuthenticationFailed method
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
SecurityTokenValidated = async (x) =>
{
var identity = x.AuthenticationTicket.Identity; //Check this.
await Task.FromResult(0);
var claims = identity.Claims;
var name = claims.First(claim => claim.Type == "name").Value;
var email = claims.First(claim => claim.Type == "preferred_username").Value;
var user = UserManager.FindByEmail(email);
var customClaims = UserManager.GetClaims(user.Id);
foreach (var claim in customClaims)
{
identity.AddClaim(new Claim(claim.Type, claim.Value));
}
HttpContext.Current.GetOwinContext().Authentication.SignIn(identity); //THis is the key here.
var principal = new ClaimsPrincipal(identity);
System.Threading.Thread.CurrentPrincipal = principal;
if (System.Web.HttpContext.Current != null)
System.Web.HttpContext.Current.User = principal;
}
}
}
);
}
And in my controller methods I am accessing my claims that I added above, using this method. Please confirm if this is correct or should I use the Thread.CurrentPrincipal somehow?
[Authorize]
public class HomeController : BaseController
{
private ApplicationUserManager _userManager;
public ActionResult Index()
{
//{
var identity = User.Identity as ClaimsIdentity;
var count = identity.Claims.Count(); //I get to see all the claims here that I set in startup
return View();
}

How to add OpenID combined with Forms Authentication to MVC

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

Identity Server Windows Authentication Claims

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 .

ASP.Net Bearer Authentication using JWT

I have implemented the normal login behavior where a JWT token is issued upon successful logon. The API (ASP.Net) should then restrict access to certain Controller actions using the [Authorize] attribute - however I keep on getting a:
401 - Unauthorized response ("Authorization has been denied for this request.")
I am relatively sure I am not configuring the JWT Token authentication correctly - however I have this in my startup, using Microsoft.Owin.Host.SystemWeb:
public void Configuration(IAppBuilder app) {
ConfigureOAuthTokenConsumption(app);
}
private void ConfigureOAuthTokenConsumption(IAppBuilder app) {
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions {
AllowedAudiences = new string[] { "*" },
TokenValidationParameters = new TokenValidationParameters {
ValidateIssuer = true,
ValidIssuer = "SomeValidIssuer",
ValidateAudience = true,
ValidAudience = "SomeValidAudience",
ValidateLifetime = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("mysupersecretkey"))
}
}
);
}
With a very simple token being passed through to the request via header:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MzM1NzY5MDEsImlzcyI6IlNvbWVWYWxpZElzc3VlciIsImF1ZCI6IlNvbWVWYWxpZEF1ZGllbmNlIn0.YfgbVuLgjTagkZTHwXRh9gQxOq5boeMa7TOgq0keKc0
You can use this site to see the contents of the token
I have followed this excellent link on Securing your ASP.NET Core 2.0 API which is working like a charm, but dotNetCore is not an option for the project I am currently working on - however I have followed a similar path in issuing the tokens.
What am I doing wrong, why am I getting a 401 on my request?

Token endpoint with AllowInsecureHttp=false still accepting http requests

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 :)

Categories

Resources