I have written a bunch of restful ASP.Net Web API and I want to authenticate some of the API. I have gone with Digest Authentication implementation from here
Also I have referred demo code from here
I have understood the code a bit but I have no idea where and how do I connect my existing database for getting data from customer table. If anyone has information regarding how to achieve this then please share.
Following are some methods for authentication:
DigestAuthorizationFilterAttributeBase.cs
protected override string GetAuthenticatedUser(HttpActionContext actionContext)
{
var auth = actionContext.Request.Headers.Authorization;
if (auth == null || auth.Scheme != Scheme)
return null;
var header = DigestHeader.Create(
actionContext.Request.Headers.Authorization.Parameter,
actionContext.Request.Method.Method);
if (!DigestNonce.IsValid(header.Nonce, header.NounceCounter))
{
return null;
}
var password = GetPassword(header.UserName);
var hash1 = String.Format(
"{0}:{1}:{2}",
header.UserName,
header.Realm,
password).ToMd5Hash();
var hash2 = String.Format(
"{0}:{1}",
header.Method,
header.Uri).ToMd5Hash();
var computedResponse = String.Format(
"{0}:{1}:{2}:{3}:{4}:{5}",
hash1,
header.Nonce,
header.NounceCounter,
header.Cnonce,
"auth",
hash2).ToMd5Hash();
return header.Response.Equals(computedResponse, StringComparison.Ordinal)
? header.UserName
: null;
}
DigestAuthorizationFilterAttribute.cs
public DigestAuthorizationFilterAttribute(bool issueChallenge = true) : base(issueChallenge)
{
}
protected override bool IsUserAuthorized(string userName)
{
return true;
}
protected override string GetPassword(string userName)
{
return userName;
}
One example would be in the following method:
protected override bool IsUserAuthorized(string userName)
{
return true;
}
You could do something roughly similar to:
protected override bool IsUserAuthorized(string userName)
{
var user = db.Users.Where(u => u.username = userName);
if(user.Any())
{
return true;
}
else
{
return false;
}
}
You would also need to check if the password is valid. But you get the idea.
Hope this helped.
Related
I tried to add the MaxLoginAttempts feature in my ServiceStack project. But it is still allowing login attempts after 5 unsuccessful login attempts. I'm not sure to know what is missing in my code.
AppHost.cs :
public override void Configure(Funq.Container container)
{
ServiceStack.Text.JsConfig.EmitCamelCaseNames = true;
Routes
.Add<Hello>("/hello")
.Add<Hello>("/hello/{Name*}");
var appSettings = new AppSettings();
//Enable a custom authentication
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new CustomAuthProvider(appSettings),
})
{
MaxLoginAttempts = 5
});
}
CustomAuthProvider.cs
public CustomAuthProvider(AppSettings appSettings) : base(appSettings) {}
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
// authentication logic
if (userName.Equals("username") && password.Equals("password"))
{
return true;
}
else
{
return false;
}
}
public override IHttpResult OnAuthenticated(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo)
{
//Saving the session!
return base.OnAuthenticated(authService, session, tokens, authInfo);
}
The MaxLoginAttempts gets validated when you validate the Username/Password against the AuthRepository:
public virtual bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
var authRepo = GetUserAuthRepository(authService.Request);
using (authRepo as IDisposable)
{
var session = authService.GetSession();
if (authRepo.TryAuthenticate(userName, password, out var userAuth))
{
if (IsAccountLocked(authRepo, userAuth))
throw new AuthenticationException(ErrorMessages.UserAccountLocked.Localize(authService.Request));
session.PopulateSession(userAuth, authRepo);
return true;
}
return false;
}
}
Since you're overriding TryAuthenticate() and not using an Auth Repository you're going to have to validate it yourself where you'll need to maintain a InvalidLoginAttempts counter wherever you're persisting the User Info.
If it helps this is what all Auth Repositories use to validate invalid password attempts:
public static void RecordInvalidLoginAttempt(this IUserAuthRepository repo, IUserAuth userAuth)
{
var feature = HostContext.GetPlugin<AuthFeature>();
if (feature?.MaxLoginAttempts == null) return;
userAuth.InvalidLoginAttempts += 1;
userAuth.LastLoginAttempt = userAuth.ModifiedDate = DateTime.UtcNow;
if (userAuth.InvalidLoginAttempts >= feature.MaxLoginAttempts.Value)
{
userAuth.LockedDate = userAuth.LastLoginAttempt;
}
repo.SaveUserAuth(userAuth);
}
Note: the recommend way to set Camel Case is to use:
SetConfig(new HostConfig { UseCamelCase = true });
For all other Serialization customization you should use JsConfig.Init(), e.g:
JsConfig.Init(new Config {
DateHandler = DateHandler.ISO8601,
AlwaysUseUtc = true,
TextCase = TextCase.CamelCase,
ExcludeDefaultValues = true,
});
I have a solution that contains 2 applications
Application (.NET Application)
ApiApplication (Console Application)
Within Application I have this controller:
public class ApiAccountController : Controller
{
ApplicationDbContext dbContext = new ApplicationDbContext();
Logger log = LogManager.GetCurrentClassLogger();
PasswordHasher passwordHasher = new PasswordHasher();
// GET: Account
public bool AuthenticateUser(String username, String password)
{
try
{
var user = dbContext.Users.FirstOrDefault(u => u.UserName == username);
//var user = dbContext.Users.Count(u => u.UserName == username);
if (user == null)
{
log.Error(username + " not found");
return false;
}
else
{
var result = passwordHasher.VerifyHashedPassword(user.PasswordHash, password);
if (result == PasswordVerificationResult.Success)
{
return true;
}
else
{
log.Error("Invalid password for user: " + username);
return false;
}
}
//return false;
}
catch (Exception e)
{
log.Error(e, "Exception found for user: " + username);
return false;
}
}
}
Within ApiApplication I have this Api Class:
public class TokenController : ApiController
{
// This is naive endpoint for demo, it should use Basic authentication to provide token or POST request
// GET api/token/
public string Get(string username, string password)
{
if (CheckUser(username, password))
{
return JwtManager.GenerateToken(username);
}
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
private bool CheckUser(string username, string password)
{
ApiAccountController accountController = new ApiAccountController();
return accountController.AuthenticateUser(username,password);
}
}
ApiApplication references Application and I want to call AuthenticateUser from Application. Previously when I tried to run it, AuthenticateUser would always return null, I managed to narrow it down to something to do with my dbContext. However, I discovered that when I placed the connectionstring from Application into ApiApplication, I get an error that says my database already exists.
The purpose of ApiApplication is only to receive and pass requests to Application and to retrieve data from ApiApplication. It should not be connected to the database at all. Am I missing something? Should I specify the connectionString for Application when ApiApplication calls the AuthenticateUser method?
I have an ASP.NET Web API 2 application that uses Windows Authentication for all the controllers. I have a need now for some controllers to use Basic Authentication.
I know it is not possible to enable both Anonymous and Windows Authentications, but is it possible to enable Windows Authentication for some controllers and Basic Authentication for some others?
UPDATE:
Implemented a filter as shown on the article EdSF shared
Here is what I have so far:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class BasicAuthenticationFilter : AuthorizationFilterAttribute
{
private bool _active = true;
private const string ValidUsername = #"Test";
private const string ValidPassword = #"T3st";
public BasicAuthenticationFilter()
{
}
public BasicAuthenticationFilter(bool active)
{
_active = active;
}
public override void OnAuthorization(HttpActionContext actionContext)
{
if (_active)
{
var identity = ParseAuthorizationHeader(actionContext);
if (identity == null)
{
Challenge(actionContext);
return;
}
if (!OnAuthorizeUser(identity.Name, identity.Password, actionContext))
{
Challenge(actionContext);
return;
}
var principal = new GenericPrincipal(identity, null);
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
base.OnAuthorization(actionContext);
}
}
protected virtual bool OnAuthorizeUser(string username, string password, HttpActionContext actionContext)
{
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password) ||
!username.Equals(ValidUsername) || !password.Equals(ValidPassword))
{
return false;
}
return true;
}
protected virtual BasicAuthenticationIdentity ParseAuthorizationHeader(HttpActionContext actionContext)
{
string authHeader = null;
var auth = actionContext.Request.Headers.Authorization;
if (auth != null && auth.Scheme == "Basic")
{
authHeader = auth.Parameter;
}
if (string.IsNullOrEmpty(authHeader)) return null;
authHeader = Encoding.Default.GetString(Convert.FromBase64String(authHeader));
var tokens = authHeader.Split(':');
if (tokens.Length < 2) return null;
return new BasicAuthenticationIdentity(tokens[0], tokens[1]);
}
private void Challenge(HttpActionContext actionContext)
{
var host = actionContext.Request.RequestUri.DnsSafeHost;
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
actionContext.Response.Headers.Add("WWW-Authenticate", string.Format("Basic realm=\"{0}\"", host));
}
}
BasicAuthenticationIdentity Class:
public class BasicAuthenticationIdentity : GenericIdentity
{
public BasicAuthenticationIdentity(string name, string password)
: base(name, "Basic")
{
Password = password;
}
public string Password { get; set; }
}
Also, decorated my controller with the Basic Authentication Filter:
[BasicAuthenticationFilter]
[RoutePrefix("api/BasicAuth")]
public class BasicAuthController : ApiController
{
//[BasicAuthenticationFilter]
[HttpGet]
public IHttpActionResult TestBasicAuth()
{
return Ok("success");
}
}
When I make a call to api/BasicAuth from Fiddler I got back 401, but it only returns the following challenges:
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
At this point Fiddler tries it again but this time instead of passing Basic Authorization Scheme, it passes Negotiate. This one fails also.
Then, when Fiddler finally tries the third time, my filter actually gets the request, but since authorization scheme is Negotiate instead of Basic, my filter returns Unauthorized also.
Is there a way to force the controller to just use the BasicAuthenticationFilter?
Thanks in advance
You can - because BASIC AUTH credentials are sent in HTTP Headers (base64 encoded only). You don't have to "enable" anything at the application level and handle HTTP requests to your API endpoints "manually" (by inspecting headers).
e.g. Basic Auth Header: Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
See this example that creates an AuthorizationFilter that can be applied to Controller or Action, or even globally if needed...
Hth.
I'm trying to implement CSRF using AntiForgeryToken from .Net Framework on a single page application. I've implemented some code inside my .csthml file and i've created an AuthorizeAttribute:
Index.cshtml
<script>
#functions{
public string GetAntiForgeryToken()
{
string cookieToken, formToken;
System.Web.Helpers.AntiForgery.GetTokens(null, out cookieToken, out formToken);
return cookieToken + ":" + formToken;
}
}
$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
var xxx = '#GetAntiForgeryToken()';
jqXHR.setRequestHeader("X-CSRF", xxx);
});
</script>
ValidateHttpAntiForgeryTokenAttribute.cs
public class ValidateHttpAntiForgeryTokenAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
var headers = actionContext.Request.Headers;
var headerToken = headers.Contains("X-CSRF") ? headers.GetValues("X-CSRF").FirstOrDefault() : null;
if (headerToken == null)
{
return false;
}
var tokenValues = headerToken.Split(':');
if (tokenValues.Length < 2)
{
return false;
}
else
{
var cookieToken = tokenValues[0];
var formToken = tokenValues[1];
try
{
AntiForgery.Validate(cookieToken, formToken);
}
catch(Exception ex)
{
return false;
}
}
return base.IsAuthorized(actionContext);
}
}
MyController.cs
[HttpGet]
[ValidateHttpAntiForgeryTokenAttribute]
public HttpResponseMessage Get()
{
...
}
Every time that ValidateHttpAntiForgeryTokenAttribute is called, i got the following error:
The provided anti-forgery token was meant for user "CMP\usr", but the current user is "dev#company.net"
I would like to know why it displays the username of computer instead the username that is logged on application and why the token isn't changing when call GetAntiForgeryToken() is executed.
Thank in advance.
As quick fix (if you don't care about username validation inside anti-CSRF logic) would be:
AntiForgeryConfig.SuppressIdentityHeuristicChecks = true
somewhere in AppStart logic (global.asax).
I have an existing, working ASP.NET MVC 4 web application. I have written my own RoleProvider and I am using the standard [Authorize] attribute. My controllers look like this:
[Authorize(Roles="ContactAdmins")] //System.Web.Mvc
public ActionResult Index()
I would like to add a WebAPI controller to my application, and take advantage of my existing plumbing
[Authorize(Roles="ContactAdmins")] //System.Web.Http
public IEnumerable<Contact> Get()
This works for Javascript ajax calls from within my site (since the browser user is already authenticated with a Forms auth cookie). My question is from a C# Console Application (or any other application that is not part of my web app) how can I authenticate to this API?
Lets assume that for the parts of my API which are public, I am using code very similar to what is found at this question Consuming WebApi in MVC3.
var url = "http://localhost:9000/api/contacts";
using (var client = new WebClient())
using (var reader = XmlReader.Create(client.OpenRead(url)))
{
var serializer = new XmlSerializer(typeof(Contact[]));
var contacts = (Contact[])serializer.Deserialize(reader);
// TODO: Do something with the contacts
}
What would I need to modify here? Or would I have to scrap this and use a completely different approach? I am not tied to using Forms for API Authentication of remote clients, but I would like to keep the current elegant approach for JavaScript clients that are part of the app (just request API since forms cookie is set).
You could combine the standard Forms Auth with a custom Basic Auth, based on the same primitives than Forms Auth. Note with Basic, HTTPS is strongly recommended (and in fact more and more Windows component do not support Basic+HTTP by default nowadays).
Here is a sample code for a Basic Authentication Module that reuses code from Forms Auth. It also comes with it's own configuration section (named 'basicAuth'). You want to make sure both auths (Forms and Basic) use the same cookie and parameters when configured together:
using System;
using System.ComponentModel;
using System.Configuration;
using System.Globalization;
using System.Net;
using System.Security.Principal;
using System.Text;
using System.Web;
using System.Web.Configuration;
using System.Web.Security;
namespace MySecurity
{
public class BasicAuthenticationModule : IHttpModule
{
public event EventHandler<BasicAuthenticationEventArgs> Authenticate;
public void Dispose()
{
}
protected virtual string GetRealm(HttpContext context)
{
return BasicAuthenticationSection.Current.GetRealm(context);
}
public virtual void Init(HttpApplication context)
{
context.AuthenticateRequest += OnAuthenticateRequest;
context.EndRequest += OnEndRequest;
}
protected virtual bool FormsAuthenticate(HttpContext context, string login, string password, string realm)
{
// check ad-hoc forms credentials, as we can support it even if forms auth is not configured
FormsAuthenticationConfiguration c = ((AuthenticationSection)ConfigurationManager.GetSection("system.web/authentication")).Forms;
if ((c.Credentials == null) || (c.Credentials.Users == null))
return false;
foreach (FormsAuthenticationUser user in c.Credentials.Users)
{
if ((string.Compare(user.Name, login, true, CultureInfo.CurrentCulture) == 0) &&
(string.Compare(user.Password, password, true, CultureInfo.CurrentCulture) == 0))
return true;
}
return false;
}
protected virtual bool OnAuthenticate(HttpContext context, string login, string password, string realm)
{
EventHandler<BasicAuthenticationEventArgs> handler = Authenticate;
if (handler != null)
{
BasicAuthenticationEventArgs e = new BasicAuthenticationEventArgs(context, login, password, realm);
handler(this, e);
return !e.Cancel;
}
return FormsAuthenticate(context, login, password, realm);
}
protected virtual string[] GetUserRoles(HttpContext context, string login, string realm)
{
// TODO: overwrite if needed
return new string[0];
}
protected virtual IPrincipal GetUser(HttpContext context, FormsAuthenticationTicket ticket)
{
return new GenericPrincipal(new BasicAuthenticationIdentity(ticket), GetUserRoles(context, ticket.Name, GetRealm(context)));
}
protected virtual void OnAuthenticated(HttpContext context)
{
}
protected virtual void OnEndRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
if (application.Response.StatusCode != (int)HttpStatusCode.Unauthorized)
return;
string basic = "Basic Realm=\"" + GetRealm(application.Context) + "\"";
application.Response.AppendHeader("WWW-Authenticate", basic);
}
public static void SignOut()
{
if (HttpContext.Current == null)
return;
HttpContext.Current.Request.Cookies.Remove(BasicAuthenticationSection.Current.Name);
HttpContext.Current.Response.Cookies.Remove(BasicAuthenticationSection.Current.Name);
HttpCookie cookie = new HttpCookie(BasicAuthenticationSection.Current.Name);
cookie.Expires = DateTime.Now.AddDays(-1);
HttpContext.Current.Response.Cookies.Add(cookie);
}
public static bool IsAuthenticated(HttpContext context)
{
if ((context == null) || (context.User == null) || (context.User.Identity == null))
return false;
return context.User.Identity.IsAuthenticated;
}
protected virtual void OnAuthenticateRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
if ((IsAuthenticated(application.Context)) && (!BasicAuthenticationSection.Current.ReAuthenticate))
return;
string encryptedTicket;
FormsAuthenticationTicket ticket;
HttpCookie cookie = application.Context.Request.Cookies[BasicAuthenticationSection.Current.Name];
if (cookie == null)
{
// no cookie, check auth header
string authHeader = application.Context.Request.Headers["Authorization"];
if ((string.IsNullOrEmpty(authHeader)) || (!authHeader.StartsWith("Basic ", StringComparison.InvariantCultureIgnoreCase)))
{
ResponseAccessDenied(application);
return;
}
string login;
string password;
string lp = authHeader.Substring(6).Trim();
if (string.IsNullOrEmpty(lp))
{
ResponseAccessDenied(application);
return;
}
lp = Encoding.Default.GetString(Convert.FromBase64String(lp));
if (string.IsNullOrEmpty(lp.Trim()))
{
ResponseAccessDenied(application);
return;
}
int pos = lp.IndexOf(':');
if (pos < 0)
{
login = lp;
password = string.Empty;
}
else
{
login = lp.Substring(0, pos).Trim();
password = lp.Substring(pos + 1).Trim();
}
if (!OnAuthenticate(application.Context, login, password, GetRealm(application.Context)))
{
ResponseAccessDenied(application);
return;
}
// send cookie back to client
ticket = new FormsAuthenticationTicket(login, false, (int)BasicAuthenticationSection.Current.Timeout.TotalMinutes);
encryptedTicket = FormsAuthentication.Encrypt(ticket);
cookie = new HttpCookie(BasicAuthenticationSection.Current.Name, encryptedTicket);
application.Context.Response.Cookies.Add(cookie);
// don't overwrite context user if it's been set
if ((!IsAuthenticated(application.Context)) || (BasicAuthenticationSection.Current.ReAuthenticate))
{
application.Context.User = GetUser(application.Context, ticket);
}
OnAuthenticated(application.Context);
application.Context.Response.StatusCode = (int)HttpStatusCode.OK;
return;
}
// there is a cookie, check it
encryptedTicket = cookie.Value;
if (string.IsNullOrEmpty(encryptedTicket))
{
ResponseAccessDenied(application);
return;
}
try
{
ticket = FormsAuthentication.Decrypt(encryptedTicket);
}
catch
{
ResponseAccessDenied(application);
return;
}
if (ticket.Expired)
{
ResponseAccessDenied(application);
return;
}
// set context user
// don't overwrite context user if it's been set
if ((!IsAuthenticated(application.Context) || (BasicAuthenticationSection.Current.ReAuthenticate)))
{
application.Context.User = GetUser(application.Context, ticket);
}
OnAuthenticated(application.Context);
}
protected virtual void WriteAccessDenied(HttpApplication application)
{
if (application == null)
throw new ArgumentNullException("application");
application.Context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
application.Context.Response.StatusDescription = "Unauthorized";
application.Context.Response.Write(application.Context.Response.StatusCode + " " + application.Context.Response.StatusDescription);
}
protected virtual void ResponseAccessDenied(HttpApplication application)
{
// if there is a bad cookie, kill it
application.Context.Request.Cookies.Remove(BasicAuthenticationSection.Current.Name);
application.Context.Response.Cookies.Remove(BasicAuthenticationSection.Current.Name);
HttpCookie cookie = new HttpCookie(BasicAuthenticationSection.Current.Name);
cookie.Expires = DateTime.Now.AddDays(-1);
HttpContext.Current.Response.Cookies.Add(cookie);
WriteAccessDenied(application);
application.CompleteRequest();
}
}
public class BasicAuthenticationSection : ConfigurationSection
{
public const string SectionName = "basicAuth";
private const string DefaultCookieName = "." + SectionName;
private static BasicAuthenticationSection _current;
public static BasicAuthenticationSection Current
{
get
{
return _current ?? (_current = ConfigurationManager.GetSection(SectionName) as BasicAuthenticationSection ?? new BasicAuthenticationSection());
}
}
[StringValidator(MinLength = 1), ConfigurationProperty("name", DefaultValue = DefaultCookieName)]
public string Name
{
get
{
return (string)base["name"];
}
}
internal string GetRealm(HttpContext context)
{
if (!string.IsNullOrEmpty(Realm))
return Realm;
return context.Request.Url.Host;
}
[ConfigurationProperty("realm", DefaultValue = "")]
public string Realm
{
get
{
return (string)base["realm"];
}
}
[ConfigurationProperty("domain", DefaultValue = "")]
public string Domain
{
get
{
return (string)base["domain"];
}
}
[ConfigurationProperty("reAuthenticate", DefaultValue = false)]
public bool ReAuthenticate
{
get
{
return (bool)base["reAuthenticate"];
}
}
[TypeConverter(typeof(TimeSpanMinutesConverter)), ConfigurationProperty("timeout", DefaultValue = "30"), PositiveTimeSpanValidator]
public TimeSpan Timeout
{
get
{
return (TimeSpan)base["timeout"];
}
}
}
public class BasicAuthenticationIdentity : IIdentity
{
public BasicAuthenticationIdentity(FormsAuthenticationTicket ticket)
{
if (ticket == null)
throw new ArgumentNullException("ticket");
Ticket = ticket;
}
public FormsAuthenticationTicket Ticket;
public string AuthenticationType
{
get
{
return BasicAuthenticationSection.SectionName;
}
}
public bool IsAuthenticated
{
get
{
return true;
}
}
public string Name
{
get
{
return Ticket.Name;
}
}
}
public class BasicAuthenticationEventArgs : CancelEventArgs
{
public BasicAuthenticationEventArgs(HttpContext context, string login, string password, string realm)
{
if (context == null)
throw new ArgumentNullException("context");
Context = context;
Login = login;
Password = password;
Realm = realm;
}
public HttpContext Context { get; private set; }
public string Realm { get; private set; }
public string Login { get; private set; }
public string Password { get; private set; }
public IPrincipal User { get; set; }
}
}
Once this is installed server side, you can configure the WebClient to use Basic auth:
WebClient client = new WebClient();
client.Credentials = new NetworkCredential("username", "password");
There are numerous ways to share the cookie with the console application. Take a look at some ideas here:
http://netpl.blogspot.com/2008/02/clickonce-webservice-and-shared-forms.html
Another simple option would be to expose a web method which doesn't require any authentication, gets the username and password and returns the cookie to the client.
No matter what approach you take, your goal is to somehow get the forms cookie at the console application side. From there you are easily done as all you do is you attach the cookie to your requests. The web api will accept the cookie happily.