I'm experiencing a situation that I have find nowhere in other articles. I'm designing a RESTful server to be consumed by an mobile app. In this case, username and password are part of header in app call, there is no logon screen.
The following code does the job of validating user info and the controllers has security controlled.
My question is: how can I populate iPrincipal in the ApiController controllers?
I have created a filter addressed at WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Filtro de login
config.Filters.Add(new tbAuthorize());
The code for tbAuthorize is:
public class tbAuthorize : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
string username;
string password;
if (GetUserNameAndPassword(actionContext, out username, out password))
{
if (!isUserAuthorized(username, password))
return false;
else
{
//Users = username;
return true;
}
}
else
return false;
}
private bool GetUserNameAndPassword(HttpActionContext actionContext, out string username, out string password)
{
username = "";
password = "";
if (actionContext.Request.Headers.Authorization == null) return false;
// Convert 64 code to separated string[]
string[] s = ParseAuthHeader(actionContext.Request.Headers.Authorization.ToString());
if (s == null)
return false;
username = s[0];
password = s[1];
return true;
}
private string[] ParseAuthHeader(string authHeader)
{
// Check this is a Basic Auth header
if (authHeader == null || authHeader.Length == 0 || !authHeader.StartsWith("Basic")) return null;
// Pull out the Credentials with are seperated by ':' and Base64 encoded
string base64Credentials = authHeader.Substring(6);
string[] credentials = Encoding.ASCII.GetString(Convert.FromBase64String(base64Credentials)).Split(new char[] { ':' });
if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0]) || string.IsNullOrEmpty(credentials[0])) return null;
// Okay this is the credentials
return credentials;
}
private bool isUserAuthorized(string username, string password)
{
// Valid the user at database
var userId = new UsersController().Login(username, password);
// Membership.GetUser() is null
//Users = Membership.GetUser().Email;
return userId != 0;
}
}
The issue is that I have no access to a cookie in Response and I did not find to way to populate iPrincipal.
I need to has data in this.User.Identity.Name, as this:
[tbAuthorize]
public class UsersController : ApiController
{
public void test()
{
string x = this.User.Identity.Name;
}
Thanks for any help,
Marco Castro
Authentication and Authorization are two differents things. Before authorizing a user you have to authenticate their.
With WebApi you have the concept of pipeline with Delegatinghandler. Message goes from one handler to the next until one send the response. I recommend you to create a DelegatingHandler to authentificate users. Then you can use AuthorizeAttribute to prevent unauthenticated user to access your API.
Here's an example to authenticate user with HTTP Basic
public abstract class BasicAuthMessageHandler : DelegatingHandler
{
private const string BasicAuthResponseHeader = "WWW-Authenticate";
private const string BasicAuthResponseHeaderValue = "Basic Realm=\"{0}\"";
protected BasicAuthMessageHandler()
{
}
protected BasicAuthMessageHandler(HttpConfiguration httpConfiguration)
{
InnerHandler = new HttpControllerDispatcher(httpConfiguration);
}
protected virtual string GetRealm(HttpRequestMessage message)
{
return message.RequestUri.Host;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
// Process request
AuthenticationHeaderValue authValue = request.Headers.Authorization;
if (authValue != null && !String.IsNullOrWhiteSpace(authValue.Parameter) &&
string.Equals(authValue.Scheme, "basic", StringComparison.OrdinalIgnoreCase))
{
// Try to authenticate user
IPrincipal principal = ValidateHeader(authValue.Parameter);
if (principal != null)
{
request.GetRequestContext().Principal = principal;
}
}
return base.SendAsync(request, cancellationToken) // Send message to the InnerHandler
.ContinueWith(task =>
{
// Process response
var response = task.Result;
if (response.StatusCode == HttpStatusCode.Unauthorized &&
!response.Headers.Contains(BasicAuthResponseHeader))
{
response.Headers.Add(BasicAuthResponseHeader,
string.Format(BasicAuthResponseHeaderValue, GetRealm(request)));
}
return response;
}, cancellationToken);
}
private IPrincipal ValidateHeader(string authHeader)
{
// Decode the authentication header & split it
var fromBase64String = Convert.FromBase64String(authHeader);
var lp = Encoding.Default.GetString(fromBase64String);
if (string.IsNullOrWhiteSpace(lp))
return null;
string login;
string password;
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();
}
return ValidateUser(login, password);
}
protected abstract IPrincipal ValidateUser(string userName, string password);
}
Write you own user validation logic. For example:
public class SampleBasicAuthMessageHandler : BasicAuthMessageHandler
{
protected override IPrincipal ValidateUser(string userName, string password)
{
if (string.Equals(userName, "Meziantou", StringComparison.OrdinalIgnoreCase) && password == "123456")
return new GenericPrincipal(new GenericIdentity(userName, "Basic"), new string[0]);
return null;
}
}
Finally you have to register the Handler
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.MessageHandlers.Add(new SampleBasicAuthMessageHandler());
You'll find a complete example on Github: https://github.com/meziantou/Samples/tree/master/Web%20Api%20-%20Basic%20Authentication
Related
I'm new in api and very much confused,I have implemented a basic authentication,it authenticate the user and I get 200 status, but then why it does not reach my controller?
my web api config:
config.Filters.Add(new BasicAuthenticationAttribute());
// config.Filters.Add(new AuthorizeAttribute());
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
// To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
// For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.
//config.EnableQuerySupport();
// To disable tracing in your application, please comment out or remove the following line of code
// For more information, refer to: http://www.asp.net/web-api
config.EnableSystemDiagnosticsTracing();
my controller:
[BasicAuthentication]
public HttpResponseMessage Get(string id, string dttimeFrom, string dttimeTo)
{
var _context = new TenMinutesContext();
var rslt = (from m in _context.users
// where m.m_turbine_id == id
select new User
{
m_user_name = m.m_user_name
// m.m_voltage_u12=
}).Take(10).ToList();
return Request.CreateResponse(HttpStatusCode.OK,
rslt);
in postman I put http://localhost:51154/api/values?id=192.168.39.82&dttimeFrom=05102019&dttimeTo=10102019
and I get the user is authenticated,but why I cant see the controller result?
my BasicAuthentication Class:
public class BasicAuthenticationAttribute: AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
var authHeader = actionContext.Request.Headers.Authorization;
if (authHeader != null)
{
var authenticationToken = actionContext.Request.Headers.Authorization.Parameter;
var decodedAuthenticationToken = Encoding.UTF8.GetString(Convert.FromBase64String(authenticationToken));
var usernamePasswordArray = decodedAuthenticationToken.Split(':');
var userName = usernamePasswordArray[0];
var password = usernamePasswordArray[1];
// Replace this with your own system of security / means of validating credentials
var isValid = userName == "rene" && password == "2019";
if (isValid)
{
var principal = new GenericPrincipal(new GenericIdentity(userName), null);
Thread.CurrentPrincipal = principal;
actionContext.Response =
actionContext.Request.CreateResponse(HttpStatusCode.OK,
"User " + userName + " successfully authenticated");
base.OnAuthorization(actionContext);
}
else
{
HandleUnathorized(actionContext);
}
}
}
private static void HandleUnathorized(HttpActionContext actionContext)
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
actionContext.Response.Headers.Add("WWW-Authenticate", "Basic Scheme='Data' location = 'http://localhost:");
}
}
}
I'm currently trying to implement my own custom PasswordSignInAsync method on the ApplicationSignInManager, but so far it fails to authenticate even though the method won't throw any errors, here is my code in IdentityConfig.cs. I have already configured ASP Identity to use long as primary keys.
I don't know if I'm missing an additional operation for creating a correct user signin, could anyone point me what I'm missing, I haven't found any code for this. This custom function passes an additional parameter to the GenerateUserIdentityAsync method of the ApplicationUser.
public class ApplicationSignInManager : SignInManager<ApplicationUser, long>
{
public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
: base(userManager, authenticationManager)
{
}
public async Task<SignInStatus> PasswordSystemSignInAsync(string userName, string password, bool IsPersistent, bool shouldLockout, bool IsAdministrative, string securityCode = null)
{
var user = await UserManager.FindByNameAsync(userName);
if(user != null)
{
bool passwordCheck = await UserManager.CheckPasswordAsync(user, password);
if (passwordCheck)
{
var signInUser = await user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager, IsAdministrative);
if (signInUser.IsAuthenticated)
{
return SignInStatus.Success;
}
return SignInStatus.Failure;
}
return SignInStatus.Failure;
}
return SignInStatus.Failure;
}
}
In the method you create the identity but you are not sign in the generated identity. Consider this:
public async Task<SignInStatus> PasswordSystemSignInAsync(string userName,string password, bool IsPersistent, bool shouldLockout, bool IsAdministrative, string securityCode = null)
{
var user = await UserManager.FindByNameAsync(userName);
if(user != null)
{
bool passwordCheck = await UserManager.CheckPasswordAsync(user, password);
if (passwordCheck)
{
var userIdentity = await user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager, IsAdministrative);
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie, DefaultAuthenticationTypes.TwoFactorCookie);
AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = IsPersistent}, userIdentity);
return SignInStatus.Success;
}
return SignInStatus.Failure;
}
return SignInStatus.Failure;
}
I want to set Individual authentication for the web API application i have created in Visual studio 2013 using Asp.net . please tell me how can i do that .
VS 2013 by default provide several types of authentication while designing . i choose individual Authentication . But don't know how it works .
Create authentication token on server-side and store it in your database or even in cache. Then send this token with requests from your win forms application. WebApi should check this token all the time. It's good enough and you have full control over your auth process.
Basically it's similar to Darin's answer.
Let me share, how it works for me:
Object with Auth details:
public class TokenIdentity
{
public int UserID { get; set; }
public string AuthToken { get; set; }
public ISocialUser SocialUser { get; set; }
}
Web API Auth Controller:
public class AuthController : ApiController
{
public TokenIdentity Post(
SocialNetwork socialNetwork,
string socialUserID,
[FromUri]string socialAuthToken,
[FromUri]string deviceRegistrationID = null,
[FromUri]DeviceType? deviceType = null)
{
var socialManager = new SocialManager();
var user = socialManager.GetSocialUser(socialNetwork, socialUserID, socialAuthToken);
var tokenIdentity = new AuthCacheManager()
.Authenticate(
user,
deviceType,
deviceRegistrationID);
return tokenIdentity;
}
}
Auth Cache Manager:
public class AuthCacheManager : AuthManager
{
public override TokenIdentity CurrentUser
{
get
{
var authToken = HttpContext.Current.Request.Headers["AuthToken"];
if (authToken == null) return null;
if (HttpRuntime.Cache[authToken] != null)
{
return (TokenIdentity) HttpRuntime.Cache.Get(authToken);
}
return base.CurrentUser;
}
}
public int? CurrentUserID
{
get
{
if (CurrentUser != null)
{
return CurrentUser.UserID;
}
return null;
}
}
public override TokenIdentity Authenticate(
ISocialUser socialUser,
DeviceType? deviceType = null,
string deviceRegistrationID = null)
{
if (socialUser == null) throw new ArgumentNullException("socialUser");
var identity = base.Authenticate(socialUser, deviceType, deviceRegistrationID);
HttpRuntime.Cache.Add(
identity.AuthToken,
identity,
null,
DateTime.Now.AddDays(7),
Cache.NoSlidingExpiration,
CacheItemPriority.Default,
null);
return identity;
}
}
Auth Manager:
public abstract class AuthManager
{
public virtual TokenIdentity CurrentUser
{
get
{
var authToken = HttpContext.Current.Request.Headers["AuthToken"];
if (authToken == null) return null;
using (var usersRepo = new UsersRepository())
{
var user = usersRepo.GetUserByToken(authToken);
if (user == null) return null;
return new TokenIdentity
{
AuthToken = user.AuthToken,
SocialUser = user,
UserID = user.ID
};
}
}
}
public virtual TokenIdentity Authenticate(
ISocialUser socialUser,
DeviceType? deviceType = null,
string deviceRegistrationID = null)
{
using (var usersRepo = new UsersRepository())
{
var user = usersRepo.GetUserBySocialID(socialUser.SocialUserID, socialUser.SocialNetwork);
user = (user ?? new User()).CopyFrom(socialUser);
user.AuthToken = System.Guid.NewGuid().ToString();
if (user.ID == default(int))
{
usersRepo.Add(user);
}
usersRepo.SaveChanges();
return new TokenIdentity
{
AuthToken = user.AuthToken,
SocialUser = user,
UserID = user.ID
};
}
}
}
Global Action Filter:
public class TokenAuthenticationAttribute : System.Web.Http.Filters.ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if (actionContext.Request.RequestUri.AbsolutePath.Contains("api/auth"))
{
return;
}
var authManager = new AuthCacheManager();
var user = authManager.CurrentUser;
if (user == null)
{
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
//Updates the authentication
authManager.Authenticate(user.SocialUser);
}
}
Global.asax registration:
GlobalConfiguration.Configuration.Filters.Add(new AuthFilterAttribute());
The idea is that AuthCacheManager extends AuthManager and decorates it's methods and properties. If there is nothing inside cache then go check database.
It is a little complicated! By default it is a Token-Based authenctication. Check these links for more details :
Individual Accounts in ASP.NET Web API: http://www.asp.net/vnext/overview/authentication/individual-accounts-in-aspnet-web-api
Understanding OWIN Forms authentication options : http://blogs.msdn.com/b/webdev/archive/2013/07/03/understanding-owin-forms-authentication-in-mvc-5.aspx#_Understanding_OWIN_Forms
also these links will help :
10 Things You Should Know about Tokens: http://blog.auth0.com/2014/01/27/ten-things-you-should-know-about-tokens-and-cookies
Cookies vs Tokens. : http://blog.auth0.com/2014/01/07/angularjs-authentication-with-cookies-vs-token/
I am very confused about this problem. Restfull service make it up to you to decide which way to implement this functionallity.
Ive read multiple articles about this problem, but every article says something different.
For example some people propopse sessions, but if you do that Web api is losing its "rest fullness". Other people suggest cockies.
I dont know if what i am done is actually done right:
On login of user i create a cockie which contains UserID(Guid) and on every request which needs user to be logged in i check if this id exsists in the DB.
Is it secure enough? Or how should i make it more secure? Or do i have to choose completly different way?
Just create authentication token on server-side and store it in your database or even in cache. Then send this token with requests from your client application. WebApi should check this token all the time. It's good enough and you have full control over your auth process.
Let me share, how it works for me:
Object with Auth details:
public class TokenIdentity
{
public int UserID { get; set; }
public string AuthToken { get; set; }
public ISocialUser SocialUser { get; set; }
}
Web API Auth Controller:
public class AuthController : ApiController
{
public TokenIdentity Post(
SocialNetwork socialNetwork,
string socialUserID,
[FromUri]string socialAuthToken,
[FromUri]string deviceRegistrationID = null,
[FromUri]DeviceType? deviceType = null)
{
var socialManager = new SocialManager();
var user = socialManager.GetSocialUser(socialNetwork, socialUserID, socialAuthToken);
var tokenIdentity = new AuthCacheManager()
.Authenticate(
user,
deviceType,
deviceRegistrationID);
return tokenIdentity;
}
}
Auth Cache Manager:
public class AuthCacheManager : AuthManager
{
public override TokenIdentity CurrentUser
{
get
{
var authToken = HttpContext.Current.Request.Headers["AuthToken"];
if (authToken == null) return null;
if (HttpRuntime.Cache[authToken] != null)
{
return (TokenIdentity) HttpRuntime.Cache.Get(authToken);
}
return base.CurrentUser;
}
}
public int? CurrentUserID
{
get
{
if (CurrentUser != null)
{
return CurrentUser.UserID;
}
return null;
}
}
public override TokenIdentity Authenticate(
ISocialUser socialUser,
DeviceType? deviceType = null,
string deviceRegistrationID = null)
{
if (socialUser == null) throw new ArgumentNullException("socialUser");
var identity = base.Authenticate(socialUser, deviceType, deviceRegistrationID);
HttpRuntime.Cache.Add(
identity.AuthToken,
identity,
null,
DateTime.Now.AddDays(7),
Cache.NoSlidingExpiration,
CacheItemPriority.Default,
null);
return identity;
}
}
Auth Manager:
public abstract class AuthManager
{
public virtual TokenIdentity CurrentUser
{
get
{
var authToken = HttpContext.Current.Request.Headers["AuthToken"];
if (authToken == null) return null;
using (var usersRepo = new UsersRepository())
{
var user = usersRepo.GetUserByToken(authToken);
if (user == null) return null;
return new TokenIdentity
{
AuthToken = user.AuthToken,
SocialUser = user,
UserID = user.ID
};
}
}
}
public virtual TokenIdentity Authenticate(
ISocialUser socialUser,
DeviceType? deviceType = null,
string deviceRegistrationID = null)
{
using (var usersRepo = new UsersRepository())
{
var user = usersRepo.GetUserBySocialID(socialUser.SocialUserID, socialUser.SocialNetwork);
user = (user ?? new User()).CopyFrom(socialUser);
user.AuthToken = System.Guid.NewGuid().ToString();
if (user.ID == default(int))
{
usersRepo.Add(user);
}
usersRepo.SaveChanges();
return new TokenIdentity
{
AuthToken = user.AuthToken,
SocialUser = user,
UserID = user.ID
};
}
}
}
Global Action Filter:
public class TokenAuthenticationAttribute : System.Web.Http.Filters.ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if (actionContext.Request.RequestUri.AbsolutePath.Contains("api/auth"))
{
return;
}
var authManager = new AuthCacheManager();
var user = authManager.CurrentUser;
if (user == null)
{
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
//Updates the authentication
authManager.Authenticate(user.SocialUser);
}
}
Global.asax registration:
GlobalConfiguration.Configuration.Filters.Add(new AuthFilterAttribute());
The idea is that AuthCacheManager extends AuthManager and decorates it's methods and properties. If there is nothing inside cache then go check database.
It's an example from real app, but I hope the idea is clear :)
Ive used couple of days trying to figugure out how to return an error to angular ajax request to web api.
in my js AccountController i have a login method:
$scope.Login = function () {
AccountService.Login($scope.UserData.LoginName, $scope.UserData.Password).success(function (account) {
$scope.UserData = account;
}).error(function () {
console.log("failed");
});
};
and in web api i have folowing:
public Account Login(string loginName, string password)
{
var emptyAccount = new Account();
password = Encrypt(password);
var account = db.Accounts.FirstOrDefault(c=>c.Password == password && c.LoginName == loginName);
if (account == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
acount.Password = "";
return account;
}
The problem is that i throw a new HttpResponseException which fire off and dont return anything back to ajax. How do i fix this?
Normally, in this case it is the error handler that will get triggered.
.error(function () {
alert("login failed");
}
This is where you could handle the error.
Also you probably want to return 401 Unauthorized in this case instead of 404. Also in general it is considered bad practice to throw exceptions in cases where you can handle it gracefully:
public HttpResponseMessage Login(string loginName, string password)
{
var emptyAccount = new Account();
password = Encrypt(password);
var account = db.Accounts.FirstOrDefault(c => c.Password == password && c.LoginName == loginName);
if (account == null)
{
return Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "unauthorized");
}
acount.Password = "";
return Request.CreateResponse(HttpStatusCode.OK, account);
}
When you sending the data, and it's hit the servers, it will return header 200 OK because it's already hit your controller, even when your controller code is throw error (in later process).
so, if you want to know what error that thrown, I will create AccountResponse Dto into that, and introduce new Error field in that like so:
public class AccountResponse()
{
public Account account { get; set;}
public string ErrorMessage { get; set; }
}
and then on the controller:
public AccountResponse Login(string loginName, string password)
{
var emptyAccount = new Account();
password = Encrypt(password);
var account = db.Accounts.FirstOrDefault(c=>c.Password == password && c.LoginName == loginName);
if (account == null)
{
return new AccountResponse(){ ErrorMessage = Response.ResponseCodes.something;
}
AccountResponse.account = account;
AccountResponse.account.Password = "";
return AccountResponse;
}