I have an api implemented under an ApiController in an MVC (Asp.net 4.5.2). In that api, I want to throw a HttpResponseException with a HttpResponseMessage(HttpStatusCode.Unauthorized) and specify a ReasonPhrase. How can this be sent directly to the client rather than having asp/mvc try to redirect them to a login page?
var message = new HttpResponseMessage(HttpStatusCode.Unauthorized);
message.ReasonPhrase = "Hello";
throw new HttpResponseException(message);
But redirect depends on Web.config settings. I think you have authentication section in web.config somethink like this:
<system.web>
<authentication mode="Forms">
<forms loginUrl="/Login/Index"></forms>
</authentication>
</system.web>
If you delete this section, redirection won't happen. But in this case you should implement authentication on your own.
Asp.Net Form Authentication module converts 401 to 302
If you using UseCookieAuthentication, then suppress this by changing OnApplyRedirect
File Startup.Auth.cs --> ConfigureAuth method --> inside app.UseCookieAuthentication(new CookieAuthenticationOptions { Provider = new CookieAuthenticationProvider { --> Add OnApplyRedirect
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
Provider = new CookieAuthenticationProvider
{
OnApplyRedirect = context =>
{
if (!context.Request.Uri.LocalPath.StartsWith(VirtualPathUtility.ToAbsolute("~/api")))
{
context.Response.Redirect(context.RedirectUri);
}
}
}
});
Related
I am trying to configure Windows authentication on a subroute only in my ASP.NET Core MVC app.
My problem is that when I add
services.AddAuthentication().AddNegotiate()
I get an error
The Negotiate Authentication handler cannot be used on a server that directly supports Windows Authentication.
which lead me to adding web.config as the docs explained:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<location path="." inheritInChildApplications="false">
<system.webServer>
<security>
<authentication>
<anonymousAuthentication enabled="false" />
<windowsAuthentication enabled="true" />
</authentication>
</security>
</system.webServer>
</location>
</configuration>
and the error goes away. However, now the Windows authentication is popping up on each request.
I tried changing the location path to .testendpoint but that then throws the original error at the base path.
So is it possible and how do I make such only /testendpoint will ask for Windows authentication and the remaining of the application will work with whatever other auth I configured in my ASP.NET Core app?
Another way using endpoint routing:
We have an application schema for the application that will be used all over the app called eavfw.
Using a custom endpoint here called login/ntlm with metadata new AuthorizeAttribute(NegotiateDefaults.AuthenticationScheme) its only allowed to be visited by a valid windows authenticated user.
Here we then create the user in our DB using its AD username.
endpoints.MapGet("/.auth/login/ntlm", async httpcontext =>
{
var loggger = httpcontext.RequestServices.GetRequiredService<ILogger<Startup>>();
var windowsAuth = await httpcontext.AuthenticateAsync(NegotiateDefaults.AuthenticationScheme);
if (!windowsAuth.Succeeded)
{
loggger.LogWarning("Not authenticated: Challening");
}
if (windowsAuth.Succeeded)
{
loggger.LogWarning("Authenticated");
var name = string.Join("\\", windowsAuth.Principal.Claims.FirstOrDefault(c => c.Type.EndsWith("name")).Value.Split("\\").Skip(1));
var context = httpcontext.RequestServices.GetRequiredService<DynamicContext>();
var users = context.Set<SystemUser>();
var user = await context.Set<SystemUser>().Where(c => c.PrincipalName == name).FirstOrDefaultAsync();
if (user == null)
{
user = new SystemUser
{
PrincipalName = name,
Name = name,
// Email = email,
};
await users.AddAsync(user);
await context.SaveChangesAsync();
}
var principal = new ClaimsPrincipal(new ClaimsIdentity(new Claim[] {
new Claim(Claims.Subject,user.Id.ToString())
}, "ntlm"))
{
};
await httpcontext.SignInAsync("ntlm",
principal, new AuthenticationProperties(
new Dictionary<string, string>
{
["schema"] = "ntlm"
}));
httpcontext.Response.Redirect("/account/login/callback");
}
}).WithMetadata(new AuthorizeAttribute(NegotiateDefaults.AuthenticationScheme));
using a auxility authentication cookie, we can now make it such that specific areas of our app that requires windows authentication, it can simply rely on Authorize("ntlm") as it automatically forward the authenticate call to check if already signin, and it as part of the signin call in the endpoint above actually sign in eavfw.external before it redirects to the general account callback page that will do some final validation before signing in eavfw from the eavfw.external cookie
services.AddAuthentication().AddCookie("ntlm", o => {
o.LoginPath = "/.auth/login/ntlm";
o.ForwardSignIn = "eavfw.external";
o.ForwardAuthenticate = "eavfw";
});
So there are a few ways to extend and use the authentication system in auth core depending on how MVC framework heavy your application is.
Just thought I'd share this tidbit of information:
First off, just because you installed Windows Authentication with Server Manager, doesn't mean it's enabled in IIS. It's NOT enabled, by default.
You have to open IIS Manager, click on your server (NOT the website - the name of the server machine hosting IIS). Then click on Authentication - you will see "Windows Authentication" is disabled. Enable it. Now it will work.
Check this is correctly set first, before making other config changes. The default project for dotNet5 and dotNet6 will work w/o any modifications if IIS is correctly configured for Windows Authentication.
In order to have a certain page/action method secured via Windows authentation, specify the corresponding authentication scheme in the action methods Authorize attribute.
[Authorize(AuthenticationSchemes = IISServerDefaults.AuthenticationScheme)]
public IActionResult UsingWindowsAuthentication()
Make sure to have Windows authentication enabled on your website.
In order to use other authentication schemes, e.g. "Individual Accounts", anonymous authentication is also enabled.
The controllers and/or action methods that must not use Windows Authentication have the default scheme specified.
For example, for an ASP.NET Core MVC project that uses the out of the box "Individual Accounts" authentication type as default authentication method, that is Identity.Application.
[Authorize(AuthenticationSchemes = "Identity.Application")]
public IActionResult Index()
See the documentation about how to set up and configure multiple authentication schemes.
I have an ASP.NET Core 2.1 WebApi, in which I have implemented JWT authentication. The user calls api/authentication/authenticate, passes their username/password in the message body, and gets back a JWT in return which they then use to access the service.
I also need the API to accept Windows authentication -- the user will call api/authentication/windows passing no user information, the service will check they are in the list of authorized users as listed in the web.config file (if I am hosting in IIS). If so, return a JWT token and the user can use that to access the service.
Currently I'm thinking about this...
The api/authentication/windows method will get the username from the request
Check the username against the list of authorized users. If they are on it, return a token. If not, go to (3)
Check against any groups in the authorized users list. If they are a member, return a token. If not, return a 401 Unauthorized error
Is this the correct way to approach this?
Very similar (unanswered) question here: Generate JWT token on successful authentication with Windows Authentication
If you want to enable both JWT and AD authentication ,in my option, you still need to validate the user's credential(username/password) against Active Directory in web api :
https://www.brechtbaekelandt.net/blog/post/authenticating-against-active-directory-with-aspnet-core-2-and-managing-users
Pass just username won't work since there is no authenticated user context in web api .
After validating user credential , you can generate jwt token as usual , for example if using HS256:
private string BuildToken()
{
var claims = new[] {
new Claim(JwtRegisteredClaimNames.NameId,"name1"),
new Claim(JwtRegisteredClaimNames.Sub,"name1"),
new Claim("customer","customer1"),
new Claim(JwtRegisteredClaimNames.Email,"wuxiyuan#sina,com"),
new Claim("role","user"),
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Youkey"));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken("name1",
"name1",
claims,
expires: DateTime.Now.AddDays(1),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
To get the username from the request to the route api/authentication/windows you should activate windows authentication for the asp.net core application. You can achieve that either modifying the web.config or enable the windows authentication in IIS.
<configuration>
<system.webServer>
<security>
<authentication>
<anonymousAuthentication enabled="true" />
<windowsAuthentication enabled="true" />
</authentication>
</security>
</system.webServer>
</configuration>
For debugging purposes modify launchSettings.json:
"iisSettings": {
"windowsAuthentication": true,
}
Leave the anonymous authentication activated: <anonymousAuthentication enabled="true" />. It is necessary in order the JWT authentication works properly for the route api/authentication/authenticate
Make sure that the attribute forwardWindowsAuthToken of the aspNetCore element in web.config is not deactivated: forwardWindowsAuthToken="true" or remove it because of the default value (true)
Add IISIntegration to the webHostBuilder unless you use a default builder: WebHost.CreateDefaultBuilder(args) - UseIISIntegration is called implicit within this extension method.
Add an Authorize attribute for the POST-method which will be mapped with the route api/authentication/windows
Test the authentication (sending windows-credentials):
var handler = new System.Net.Http.HttpClientHandler()
{
Credentials = System.Net.CredentialCache.DefaultCredentials
};
var httpClient = new System.Net.Http.HttpClient(handler)
{
BaseAddress = new Uri("http://localhost")
};
var response = await httpClient.PostAsync("api/authentication/windows", null);
or using XMLHttpRequest object:
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://localhost/api/authentication/windows', true);
xhr.withCredentials = true;
xhr.send();
Get the user name in the controller:
var username = HttpContext.User.FindFirst(System.Security.Claims.ClaimTypes.Name)?.Value;
Generate a JWT-Token, e.g using jose-jwt:
var claims = new Dictionary<string, object>
{
["jti"] = Guid.NewGuid(),
["sub"] = username,
["exp"] = DateTimeOffset.UtcNow.AddMinutes(100).ToUnixTimeSeconds()
};
var secretKey = new byte[] { 164, 60, 194, 0, 161 };
var headers = new Dictionary<string, object>
{
["alg"] = "HS512",
["typ"] = "JWT"
};
var token = JWT.Encode(claims, secretKey, JwsAlgorithm.HS512, headers);
I'm bulding a web api with owin and JWT, when I execute the project it shows me an exception. This is a method that consume the JWT, I'm having problems with as:AudienceId and as:AudienceSecret, it tells me that AudienceSecret is null. What can be the issue?
private void ConfigureOAuthTokenConsumption(IAppBuilder app)
{
var issuer = "http://localhost:59822";
string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
byte[] audienceSecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["as:AudienceSecret"]);
// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audienceId },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
}
});
}
This statement is looking for the appSettings key as:AudienceSecret in web.config file.
byte[] audienceSecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["as:AudienceSecret"]);
Since the result audienceSecret is null. The statement is not finding the key as:AudienceSecret. The most reasonable explanation is the key is missing from web.config. You need to add the key to web.config like this.Something like this
<appSettings>
<add key="as:AudienceId" value="414e1927a3884f68abc79f7283837fd1" />
<add key="as:AudienceSecret" value="qMCdFDQuF23RV1Y-1Gq9L3cF3VmuFwVbam4fMTdAfpo" />
</appSettings>
You may want to use different values cause it's ... a secret.
Our company has SSO site where all our applications gets redirected if the user is not authenticated. The SSO site authenticates the user using Forms authentication. and its been working for all the applications.(ASP.NET applications)
Now we have new MVC5 application created using VS 2013. I am trying to use Forms Authentication. If the user is not authenticated I want to redirect the user to login url ( SSO site). Below is my code. But when I debug, the user is always Authenticated.
IsAutheticated property is true, AuthenticationType is "Negotiate", and Identity is "Windows" ( even though in config file its "Forms")
(Note I am debugging in VS with IIS express if that make difference. Also it's MVC 5 application, is it because of OWIN. How do I know?)
<system.web>
<compilation debug="true" targetFramework="4.5" />
<authentication mode="Forms" >
<forms loginUrl="/Account/Login"></forms>
</authentication>
<authorization>
<deny users="?" />
</authorization>
public class AccountController : Controller
{
public ActionResult Login()
{
string loginUrl = AppSettings.Authentication.LoginUrl;
string failOverUrl = AppSettings.Authentication.FailoverLoginUrl;
string securityGroup = AppSettings.Authentication.SecurityGroup;
if (!User.Identity.IsAuthenticated) // IsAutheticated is always true, why?
{
var returnUrl = "someresturnurl";
MyAuthenticator.Authenticate(loginUrl, failOverUrl, returnUrl, securityGroup);
}
else
{
// Redirect the user if they are already authenticated.
return RedirectToAction("Index", "Home");
}
}
}
In ASP.NET MVC5 authentication mechanism has changed significantly.
Possibly all you authentication configuration comes from OWIN Startup class.
Here is a link where you can find how this configuration may look like
HttpListener listener = (HttpListener)app.Properties["System.Net.HttpListener"];
listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication;
This is a good topic about ASP.NET identity basics
I hope this will help.
I'm trying to add to a MVC4 webapi project the simple membership provider authentication mechanism found in a MVC 4 web application project, for a hybrid application serving its pages with a rich JS content, which uses AJAX calls to webapi actions to perform its tasks. I need the app users to authenticate before they can work with the apps provided in these pages, so I think I'll be fine with the forms authentication. I thus need to add it to the existing WebApi project and let my authorized-only actions return a 302 (redirect user to login page) rather than a 401.
Anyway, I'm missing something because as soon as I try to use a WebSecurity method I get the following exception:
System.InvalidOperationException was caught
Message=To call this method, the "Membership.Provider" property must be an instance of "ExtendedMembershipProvider".
Source=WebMatrix.WebData
Could anyone suggest a fix? Here are the steps I took for adding authorization:
1) Web.config: add to system.web:
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880" />
</authentication>
Add to appsettings (the 2nd entry is for replacing 401 with 302):
<add key="enableSimpleMembership" value="true"/>
<add key="webapi:EnableSuppressRedirect" value="false" />
Also remove profile, membership and rolemanager sections from the original template (they are not intended to be used with simple membership).
2) add NuGet packages for OpenAuth (DotNetOpenAuth Core, DotNetOpenAuth ext for ASP.NET, DotNetOpenAuth 1.0(a) consumer, DotNetOpenAuth 1.0(a), DotNetOpenAuth OpenID Core, DotNetOpenAuth OpenID Relying Party).
3) add InitializeSimpleMembership.cs to Filters (the code is pretty standard, see below).
4) copy from an MVC web app project the models in AccountModels.cs, all the views in Views/Account, and the AccountController.cs.
The InitializeSimpleMembership code is here:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class InitializeSimpleMembershipAttribute : ActionFilterAttribute
{
private static SimpleMembershipInitializer _initializer;
private static object _initializerLock = new object();
private static bool _isInitialized;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
}
private class SimpleMembershipInitializer
{
private static void SeedData()
{
// seed data: users and roles
if (!WebSecurity.UserExists("TheAdminGuyName"))
WebSecurity.CreateUserAndAccount("TheAdminGuyName", "password");
if (!Roles.RoleExists("administrator")) Roles.CreateRole("administrator");
if (!Roles.IsUserInRole("TheAdminGuyName", "administrator"))
Roles.AddUserToRole("TheAdminGuyName", "administrator");
}
public SimpleMembershipInitializer()
{
Database.SetInitializer<UsersContext>(null);
try
{
using (var context = new UsersContext())
{
if (!context.Database.Exists())
{
((IObjectContextAdapter)context).ObjectContext.CreateDatabase();
}
}
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "User", "UserId", "UserName", autoCreateTables: true);
SeedData();
}
catch (Exception ex)
{
throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex);
}
}
}
}
This might be relevant, as it mentions your error specifically:
http://weblogs.asp.net/jgalloway/archive/2012/08/29/simplemembership-membership-providers-universal-providers-and-the-new-asp-net-4-5-web-forms-and-asp-net-mvc-4-templates.aspx