MVC WebAPI authentication from Windows Forms - c#

I am attempting to make a Windows Forms application that plugs into some services exposed by ASP.NET MVC WebAPI, but am having a great deal of trouble with the authentication/login part.
I cannot seem to find an example that just demonstrates how to do this from Windows Forms, everything I find seems to be very convoluted and includes a lot of very deep plumbing, or seems targeted to other ASP.NET websites, and not windows forms.
Is there something I am missing? Is this just not possible? Or is it just not intended? I've looked at things like this .NET WebApi Authentication that claim to do it, but I don't see how to use cookies from a Windows Forms standpoint. I've also gone over http://blogs.msdn.com/b/webdev/archive/2012/08/26/asp-net-web-api-and-httpclient-samples.aspx and still have had very little luck.

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 win forms 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.

You could use token based authentication. Here's a great article illustrating how you could write a custom action filter that uses RSA public/private cryptography.

Related

Client specific role based authentication?

Currently, I am authenticating users in my application using role based authentication with OAuth and WebApi. I've set this up like so:
public override async Task GrantResourceOwnerCredentials (OAuthGrantResourceOwnerCredentialsContext context)
{
var user = await AuthRepository.FindUser(context.UserName, context.Password);
if (user === null)
{
context.SetError("invalid_grant", "The username or password is incorrect");
return;
}
var id = new ClaimsIdentity(context.Options.AuthenticationType);
id.AddClaim(New Claim(ClaimTypes.Name, context.UserName));
foreach (UserRole userRole in user.UserRoles)
{
id.AddClaim(new Claim(ClaimTypes.Role, userRole.Role.Name));
}
context.Validated(id);
}
Protecting my API routes with the <Authorize> tag.
I've since, however, run into an issue where my users can hold different roles for different clients. For example:
User A can be associated to multiple clients: Client A and Client B.
User A can have different "roles" when accessing information from either client. So User A may be an Admin for Client A and a basic User for Client B.
Which means, the following example:
[Authorize(Roles = "Admin")]
[Route("api/clients/{clientId}/billingInformation")]
public IHttpActionResult GetBillingInformation(int clientId)
{
...
}
User A may access billing information for Client A, but not for Client B.
Obviously, what I have now won't work for this type of authentication. What would be the best way to set up Client specific Role based authentication? Can I simply change up what I have now, or would I have to set it up a different way entirely?
You could remove the authorize tag and do the role validation inside the function instead.
Lambda solution:
Are there roles that are added based on CustomerID and UserID?
If so you could do something like the example below where you get the customer based of the values you have and then return the response.
string userID = RequestContext.Principal.Identity.GetUserId();
var customer = Customer.WHERE(x => x.UserID == userID && x.clientId == clientId && x.Roles == '1')
Can you provide us with abit more information about what you use to store the connection/role between the Customer and User.
EDIT:
Here is an example on how you could use the ActionFilterAttribute. It gets the CustomerId from the request and then takes the UserId of the identity from the request. So you can replace [Authorize] with [UserAuthorizeAttribute]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class UserAuthorizeAttribute : System.Web.Http.Filters.ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
try
{
var authHeader = actionContext.Request.Headers.GetValues("Authorization").First();
if (string.IsNullOrEmpty(authHeader))
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
{
Content = new StringContent("Missing Authorization-Token")
};
return;
}
ClaimsPrincipal claimPrincipal = actionContext.Request.GetRequestContext().Principal as ClaimsPrincipal;
if (!IsAuthoticationvalid(claimPrincipal))
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
{
Content = new StringContent("Invalid Authorization-Token")
};
return;
}
if (!IsUserValid(claimPrincipal))
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
{
Content = new StringContent("Invalid User name or Password")
};
return;
}
//Finally role has perpession to access the particular function
if (!IsAuthorizationValid(actionContext, claimPrincipal))
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
{
Content = new StringContent("Permission Denied")
};
return;
}
}
catch (Exception ex)
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
{
Content = new StringContent("Missing Authorization-Token")
};
return;
}
try
{
//AuthorizedUserRepository.GetUsers().First(x => x.Name == RSAClass.Decrypt(token));
base.OnActionExecuting(actionContext);
}
catch (Exception)
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
{
Content = new StringContent("Unauthorized User")
};
return;
}
}
private bool IsAuthoticationvalid(ClaimsPrincipal claimPrincipal)
{
if (claimPrincipal.Identity.AuthenticationType.ToLower() == "bearer"
&& claimPrincipal.Identity.IsAuthenticated)
{
return true;
}
return false;
}
private bool IsUserValid(ClaimsPrincipal claimPrincipal)
{
string userID = claimPrincipal.Identity.GetUserId();
var securityStamp = claimPrincipal.Claims.Where(c => c.Type.Equals("AspNet.Identity.SecurityStamp", StringComparison.OrdinalIgnoreCase)).Single().Value;
var user = _context.AspNetUsers.Where(x => x.userID.Equals(userID, StringComparison.OrdinalIgnoreCase)
&& x.SecurityStamp.Equals(securityStamp, StringComparison.OrdinalIgnoreCase));
if (user != null)
{
return true;
}
return false;
}
private bool IsAuthorizationValid(HttpActionContext actionContext, ClaimsPrincipal claimPrincipal)
{
string userId = claimPrincipal.Identity.GetUserId();
string customerId = (string)actionContext.ActionArguments["CustomerId"];
return AllowedToView(userId, customerId);
}
private bool AllowedToView(string userId, string customerId)
{
var customer = _context.WHERE(x => x.UserId == userId && x.CustomerId == customerId && x.RoleId == '1')
return false;
}
}
Personally I think you need to move away from using the [Authorize] attribute entirely. It's clear that your requirements for authorisation are more complex than that method "out-the-box" was intended for.
Also in the question about I think Authentication and Authorisation are being used interchangably. What we are dealing with here is Authorisation.
Since you are using Identity and claims based authorisation. I would look at doing this "on-the-fly" so to speak. Along with claims you could make use of dynamic policy generation as well as service based Authorisation using IAuthorizationRequirement instances to build up complex rules and requirements.
Going into depth on the implementation of this is a big topic but there are some very good resources available. The original approach (that I have used myself) was orginally detailed by Dom and Brock of IdentityServer fame.
They did a comprehensive video presentation on this at NDC last year which you can watch here.
Based closely on the concepts discussed in this video Jerrie Pelser blogged about a close implementation which you can read here.
The general components are:
The [Authorize] attributes would be replaced by policy generator such as:
public class AuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider
{
private readonly IConfiguration _configuration;
public AuthorizationPolicyProvider(IOptions<AuthorizationOptions> options, IConfiguration configuration) : base(options)
{
_configuration = configuration;
}
public override async Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
// Check static policies first
var policy = await base.GetPolicyAsync(policyName);
if (policy == null)
{
policy = new AuthorizationPolicyBuilder()
.AddRequirements(new HasScopeRequirement(policyName, $"https://{_configuration["Auth0:Domain"]}/"))
.Build();
}
return policy;
}
}
And then you would author any instances of IAuthorizationRequirement required to ensure users are authroised properly, an example of that would be something like:
public class HasScopeRequirement : IAuthorizationRequirement
{
public string Issuer { get; }
public string Scope { get; }
public HasScopeRequirement(string scope, string issuer)
{
Scope = scope ?? throw new ArgumentNullException(nameof(scope));
Issuer = issuer ?? throw new ArgumentNullException(nameof(issuer));
}
}
Dom and Brock then also detail a client implementation that ties all of this together which might look something like this:
public class AuthorisationProviderClient : IAuthorisationProviderClient
{
private readonly UserManager<ApplicationUser> userManager;
private readonly RoleManager<IdentityRole> roleManager;
public AuthorisationProviderClient(
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager)
{
this.userManager = userManager;
this.roleManager = roleManager;
}
public async Task<bool> IsInRole(ClaimsPrincipal user, string role)
{
var appUser = await GetApplicationUser(user);
return await userManager.IsInRoleAsync(appUser, role);
}
public async Task<List<Claim>> GetAuthorisationsForUser(ClaimsPrincipal user)
{
List<Claim> claims = new List<Claim>();
var appUser = await GetApplicationUser(user);
var roles = await userManager.GetRolesAsync(appUser);
foreach (var role in roles)
{
var idrole = await roleManager.FindByNameAsync(role);
var roleClaims = await roleManager.GetClaimsAsync(idrole);
claims.AddRange(roleClaims);
}
return claims;
}
public async Task<bool> HasClaim(ClaimsPrincipal user, string claimValue)
{
Claim required = null;
var appUser = await GetApplicationUser(user);
var userRoles = await userManager.GetRolesAsync(appUser);
foreach (var userRole in userRoles)
{
var identityRole = await roleManager.FindByNameAsync(userRole);
// this only checks the AspNetRoleClaims table
var roleClaims = await roleManager.GetClaimsAsync(identityRole);
required = roleClaims.FirstOrDefault(x => x.Value == claimValue);
if (required != null)
{
break;
}
}
if (required == null)
{
// this only checks the AspNetUserClaims table
var userClaims = await userManager.GetClaimsAsync(appUser);
required = userClaims.FirstOrDefault(x => x.Value == claimValue);
}
return required != null;
}
private async Task<ApplicationUser> GetApplicationUser(ClaimsPrincipal user)
{
return await userManager.GetUserAsync(user);
}
}
Whilst this implementation doesn't address your exact requirements (which would be hard to do anyway), this is almost certainly the approach that I would adopt given the scenario you illustrated in the question.
One solution would be to add the clients/user relationship as part of the ClaimsIdentity, and then check that with a derived AuthorizeAttribute.
You would extend the User object with a Dictionary containing all their roles and the clients that they are authorized for in that role - presumably contained in your db:
public Dictionary<string, List<int>> ClientRoles { get; set; }
In your GrantResourceOwnerCredentials method, you would add these as individual Claims with the Client Ids as the value:
foreach (var userClientRole in user.ClientRoles)
{
oAuthIdentity.AddClaim(new Claim(userClientRole.Key,
string.Join("|", userClientRole.Value)));
}
And then create a custom attribute to handle reading the claims value. The slightly tricky part here is getting the clientId value. You have given one example where it is in the route, but that may not be consistent within your application. You could consider passing it explicitly in a header, or derive whatever URL / Route parsing function works in all required circumstances.
public class AuthorizeForCustomer : System.Web.Http.AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
var isAuthorized = base.IsAuthorized(actionContext);
string clientId = ""; //Get client ID from actionContext.Request;
var user = actionContext.ControllerContext.RequestContext.Principal as ClaimsPrincipal;
var claim = user.FindFirst(this.Roles);
var clientIds = claim.Value.Split('|');
return isAuthorized && clientIds.Contains(clientId);
}
}
And you would just swap
[Authorize(Roles = "Admin")] for [AuthorizeForCustomer(Roles = "Admin")]
Note that this simple example would only work with a single role, but you get the idea.
The requirement is about having users with different authorizations. Don't feel oblige to strictly match a user permissions/authorizations with his roles. Roles are part of user identity and should not depend from client.
I will suggest to decompose the requirement:
Only Admin user can access Billing domain/subsystem
//Role-based authorization
[Authorize(Roles = "Admin")]
public class BillingController {
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-2.1
User A may access billing information for Client A, but not for Client B.
Attribute could not work here because we need at least to load the concerned client.
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/resourcebased?view=aspnetcore-2.1&tabs=aspnetcore2x
There is not a built-in way to solve this. You have to define your own merchant access right configuration system.
It might be a simple many to many table (1 user can access N merchants, 1 merchant can be accessed by N users)
public IHttpActionResult GetBillingInformation(int clientId)
{
var merchant = clientRepository.Get(clientId);
if(!UserIsConfiguredToAccessMerchant(User, merchant))
return Unauthorized();
}
NB: Claims should contain only user identity data (name, email, roles, ...). Adding authorizations, access rights claims in the token is not a good choice in my opion:
The token size might increase drastically
The user might have different authorizations regarding the domain
context or the micro service
Below some usefuls links:
https://learn.microsoft.com/en-us/dotnet/framework/security/claims-based-identity-model
https://leastprivilege.com/2016/12/16/identity-vs-permissions/
https://leastprivilege.com/2014/06/24/resourceaction-based-authorization-for-owin-and-mvc-and-web-api/

Custom authentication in ASP.NET MVC using session

There are many questions about custom authentication in ASP.NET, but none of them answers how to fully integrate it with ASP.NET mechanics.
I want to make a web application for system which already exists and have users.
Let's create new ASP.NET MVC project. I choose "No authentication" to make it from scratch (because I need to read from custom tables, custom hashing function etc, so I'll go this way).
I'll use IIdentity and IPrincipal interfaces to carry logged in user in HttpContext.
public class Identity : IIdentity
{
public Identity(string name)
{
Name = name;
IsAuthenticated = true;
}
public string Name { get; private set; }
public string AuthenticationType { get; set; }
public bool IsAuthenticated { get; set; }
}
public class Principal : IPrincipal
{
public Principal(string email)
{
Identity = new Identity(email);
}
public IIdentity Identity { get; private set; }
public bool IsInRole(string role)
{
return false;
}
}
I'll create SessionsController which will create and destroy Session. Session will contain Id of logged in user.
public class UserManager
{
public bool Authenticate(WorkerDTO user, string password)
{
using (var context = new DatabaseContext())
{
var user = context.Users.SingleOrDefault(w => w.Email == user.Email);
if (user != null)
{
// compute SHA512 of password with salt
var hash = Hash(user.Password);
if (user.HashPassword == hash)
return true;
}
return false;
}
}
}
public class SessionsController : Controller
{
private IDatabaseManager _dbManager;
private UserManager _userManager;
public SessionsController(IDatabaseManager dbManager, UserManager userManager)
{
_dbManager = dbManager;
_userManager = userManager;
}
[HttpGet]
public ActionResult Login()
{
return View();
}
[HttpPost]
public ActionResult Login(UserLoginViewModel model)
{
var user = _dbManager.Give(new WorkerByEmail(model.Email));
if(user != null && _userManager.Authenticate(user, model.Password))
{
// create session
Session["UserId"] = worker.Id;
// assign User property
User = new Principal(worker.Email);
return RedirectToAction("Index", "Home");
}
return RedirectToAction("New");
}
public ActionResult Logout()
{
Session.Remove("UserId");
User = null;
return View();
}
}
Application won't remember that User = new Principal(worker.Email);.
So, if I want to make use of sessions, I want to tell my ASP.NET application that User behind UserId carried by session (probably a cookie) each request is the logged in user. In Global.asax I have available events with reasonable names:
AuthenticateRequest
PostAuthenticateRequest.
Unfortunately, Session is unavailable in these events.
How to make use of ASP.NET in my application? Maybe Session isn't the way to go?
Should I create new IPrincipal object and attach to User prop each request?
And why User property isn't null at start? Should I set it to null on logout?

How use AuthorizationFilterAttribute in WebApi with WebClient Library?

I use the following code for Authorization (I found it in internet and change it for my use)
when i call my url seems authorization works
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class ClientAuthorizationAttribute : AuthorizationFilterAttribute
{
private bool _active = true;
public ClientAuthorizationAttribute()
{
}
public ClientAuthorizationAttribute(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;
base.OnAuthorization(actionContext);
}
}
protected virtual bool OnAuthorizeUser(string clientId, string authId, HttpActionContext actionContext)
{
return false;
}
protected virtual ClientAuthenticationIdentity 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.UTF8.GetString(Convert.FromBase64String(authHeader));
var tokens = authHeader.Split(':');
if (tokens.Length < 2)
return null;
return new ClientAuthenticationIdentity(tokens[0], tokens[1]);
}
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));
}
}
public class ClientAuthenticationIdentity : GenericIdentity
{
public ClientAuthenticationIdentity(string name, string password)
: base(name, "Basic")
{
Password = password;
}
public string Password { get; set; }
}
public class BasicAuthorizationAttribute : ClientAuthorizationAttribute
{
public BasicAuthorizationAttribute()
{ }
public BasicAuthorizationAttribute(bool active)
: base(active)
{ }
protected override bool OnAuthorizeUser(string clientId, string authId, HttpActionContext actionContext)
{
var businness = new WebServiceAuthBusiness();
return businness.Count(x => x.ClientID == clientId && x.AuthenticateID == authId) > 0;
}
}
}
in Client I use WebClient for Get application data (Does not work)
[BasicAuthorization]
public IList<Application> Get()
{
using (var client = new WebClient())
{
client.BaseAddress = _baseAddress;
client.Encoding = Encoding.UTF8;
client.UseDefaultCredentials = true; ???
client.Credentials = new NetworkCredential(clientId, authId); ???
var str = client.DownloadString("api/application/get");
return JsonConvert.DeserializeObject<List<Application>>(str);
}
}
How i can send username and password with webClient for AuthorizationFilter ???
As described on c# corner:
Add BasicAuthenticationAttribute.cs class in your solution.
With following code
public class BasicAuthenticationAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
try
{
if (actionContext.Request.Headers.Authorization == null)
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
else
{
// Gets header parameters
string authenticationString = actionContext.Request.Headers.Authorization.Parameter;
string originalString = Encoding.UTF8.GetString(Convert.FromBase64String(authenticationString));
// Gets username and password
string usrename = originalString.Split(':')[0];
string password = originalString.Split(':')[1];
AuthsController auth = new AuthsController();
// Validate username and password
if (!auth.ValidateUser(usrename, password))
{
// returns unauthorized error
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
base.OnAuthorization(actionContext);
}
// Handling Authorize: Basic <base64(username:password)> format.
catch(Exception e)
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
}
In AuthsController.cs(Entity Framework)
Add
[NonAction]
public bool ValidateUser(string userName, string password)
{
// Check if it is valid credential
var queryable = db.Auths
.Where(x => x.Name == userName)
.Where(x => x.Password == password);
if (queryable != null)
{
return true;
}
else
{
return false;
}
}
In WebApiConfig.cs
Add
config.Filters.Add(new BasicAuthenticationAttribute());
In Your controller that requires Basic Authorization.
Add
[BasicAuthentication]
public class YourController : ApiController{.......}
Basic authentication requires Authorization header to be set:
using (var client = new WebClient())
{
var credential = String.Format("{0}:{1}", userName, password);
var encodedCredential = Convert.ToBase64String(Encoding.UTF8.GetBytes(credential))
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", encodedCredential);
// ...
}
You should be able to send User name and encrypted Password as part of GET api URL.
/api/application/Get?user=''&pw=''
Your AuthorizationFilter should be able to parse them from RequestUri but of course you never want to do that, instead you might need to implement OAuth Token style access token send along with your API. Basically your user will use login panel and POST through https the login details and receive a token and then every time he or she makes request will send the access token along with that api like this:
/api/application/Get?access_token=""
This access token might expire after certain period of time or rate limitation.
You can find an implementation here:
http://www.asp.net/web-api/overview/security/individual-accounts-in-web-api

ServiceStack Authentication with Existing Database

I've been looking at ServiceStack and I'm trying to understand how to use BasicAuthentication on a service with an existing database. I would like to generate a public key (username) and secret key (password) and put that in an existing user record. The user would then pass that to the ServiceStack endpoint along with their request.
What do I need to implement in the ServiceStack stack to get this working?
I have looked at both IUserAuthRepository and CredentialsAuthProvider base class and it looks like I should just implement IUserAuthRepository on top of my existing database tables.
I am also trying to figure out what is the bare minimum I should implement to get authentication working. I will not be using the service to Add or Update user access to the Service, but instead using a separate web application.
Any help and past experiences are greatly appreciated.
Example of authenticating against an existing database (in this case via Umbraco/ASP.NET membership system). 1) Create your AuthProvider (forgive the verbose code, and note you don't have to override TryAuthenticate too, this is done here to check if the user is a member of specific Umbraco application aliases):
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Web.Security;
using ServiceStack.Configuration;
using ServiceStack.Logging;
using ServiceStack.ServiceInterface;
using ServiceStack.ServiceInterface.Auth;
using ServiceStack.WebHost.Endpoints;
using umbraco.BusinessLogic;
using umbraco.providers;
public class UmbracoAuthProvider : CredentialsAuthProvider
{
public UmbracoAuthProvider(IResourceManager appSettings)
{
this.Provider = "umbraco";
}
private UmbracoAuthConfig AuthConfig
{
get
{
return EndpointHost.AppHost.TryResolve<UmbracoAuthConfig>();
}
}
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
{
ILog log = LogManager.GetLogger(this.GetType());
var membershipProvider = (UsersMembershipProvider)Membership.Providers["UsersMembershipProvider"];
if (membershipProvider == null)
{
log.Error("UmbracoAuthProvider.OnAuthenticated - NullReferenceException - UsersMembershipProvider");
session.IsAuthenticated = false;
return;
}
MembershipUser user = membershipProvider.GetUser(session.UserAuthName, false);
if (user == null)
{
log.ErrorFormat(
"UmbracoAuthProvider.OnAuthenticated - GetMembershipUser failed - {0}", session.UserAuthName);
session.IsAuthenticated = false;
return;
}
if (user.ProviderUserKey == null)
{
log.ErrorFormat(
"UmbracoAuthProvider.OnAuthenticated - ProviderUserKey failed - {0}", session.UserAuthName);
session.IsAuthenticated = false;
return;
}
User umbracoUser = User.GetUser((int)user.ProviderUserKey);
if (umbracoUser == null || umbracoUser.Disabled)
{
log.WarnFormat(
"UmbracoAuthProvider.OnAuthenticated - GetUmbracoUser failed - {0}", session.UserAuthName);
session.IsAuthenticated = false;
return;
}
session.UserAuthId = umbracoUser.Id.ToString(CultureInfo.InvariantCulture);
session.Email = umbracoUser.Email;
session.DisplayName = umbracoUser.Name;
session.IsAuthenticated = true;
session.Roles = new List<string>();
if (umbracoUser.UserType.Name == "Administrators")
{
session.Roles.Add(RoleNames.Admin);
}
authService.SaveSession(session);
base.OnAuthenticated(authService, session, tokens, authInfo);
}
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
ILog log = LogManager.GetLogger(this.GetType());
var membershipProvider = (UsersMembershipProvider)Membership.Providers["UsersMembershipProvider"];
if (membershipProvider == null)
{
log.Error("UmbracoAuthProvider.TryAuthenticate - NullReferenceException - UsersMembershipProvider");
return false;
}
if (!membershipProvider.ValidateUser(userName, password))
{
log.WarnFormat("UmbracoAuthProvider.TryAuthenticate - ValidateUser failed - {0}", userName);
return false;
}
MembershipUser user = membershipProvider.GetUser(userName, false);
if (user == null)
{
log.ErrorFormat("UmbracoAuthProvider.TryAuthenticate - GetMembershipUser failed - {0}", userName);
return false;
}
if (user.ProviderUserKey == null)
{
log.ErrorFormat("UmbracoAuthProvider.TryAuthenticate - ProviderUserKey failed - {0}", userName);
return false;
}
User umbracoUser = User.GetUser((int)user.ProviderUserKey);
if (umbracoUser == null || umbracoUser.Disabled)
{
log.WarnFormat("UmbracoAuthProvider.TryAuthenticate - GetUmbracoUser failed - {0}", userName);
return false;
}
if (umbracoUser.UserType.Name == "Administrators"
|| umbracoUser.GetApplications()
.Any(app => this.AuthConfig.AllowedApplicationAliases.Any(s => s == app.alias)))
{
return true;
}
log.WarnFormat("UmbracoAuthProvider.TryAuthenticate - AllowedApplicationAliases failed - {0}", userName);
return false;
}
}
public class UmbracoAuthConfig
{
public UmbracoAuthConfig(IResourceManager appSettings)
{
this.AllowedApplicationAliases = appSettings.GetList("UmbracoAuthConfig.AllowedApplicationAliases").ToList();
}
public List<string> AllowedApplicationAliases { get; private set; }
}
2) Register provider via usual AppHost Configure method:
public override void Configure(Container container)
{
// .... some config code omitted....
var appSettings = new AppSettings();
AppConfig = new AppConfig(appSettings);
container.Register(AppConfig);
container.Register<ICacheClient>(new MemoryCacheClient());
container.Register<ISessionFactory>(c => new SessionFactory(c.Resolve<ICacheClient>()));
this.Plugins.Add(
new AuthFeature(
// using a custom AuthUserSession here as other checks performed here, e.g. validating Google Apps domain if oAuth enabled/plugged in.
() => new CustomAuthSession(),
new IAuthProvider[] { new UmbracoAuthProvider(appSettings)
}) {
HtmlRedirect = "/api/login"
});
}
3) Can now authenticate against existing Umbraco database # yourapidomain/auth/umbraco, using Umbraco to manage users/access to API. No need to implement extra user keys/secrets or BasicAuthentication, unless you really want to....
I'm just starting with ServiceStack and I needed exactly the same thing - and I managed to get it to work today.
The absolute bare minimum for logging in users via Basic Auth is this:
using ServiceStack.ServiceInterface;
using ServiceStack.ServiceInterface.Auth;
public class CustomBasicAuthProvider : BasicAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
// here, you can get the user data from your database instead
if (userName == "MyUser" && password == "123")
{
return true;
}
return false;
}
}
...and register it in the AppHost:
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new CustomBasicAuthProvider()
}) { HtmlRedirect = null });
That's all!
Another possible solution would be to use the default BasicAuthProvider and provide an own implementation of IUserAuthRepository instead.
I can show you an example of this as well, if you're interested.
EDIT:
Here's the bare minimum IUserAuthRepository - just inherit from InMemoryAuthRepository and override TryAuthenticate:
using ServiceStack.ServiceInterface.Auth;
public class CustomAuthRepository : InMemoryAuthRepository
{
public override bool TryAuthenticate(string userName, string password, out UserAuth userAuth)
{
userAuth = null;
if (userName == "MyUser" && password == "123")
{
userAuth = new UserAuth();
return true;
}
return false;
}
}
...and register it in the AppHost:
container.Register<IUserAuthRepository>(r => new CustomAuthRepository());
Of course, you need to register one of the default AuthProviders (Basic, Credentials, whatever) as well.

Review my ASP.NET Authentication code

I have had some problems with authentication in ASP.NET. I'm not used most of the built in authentication in .NET.
I gotten some complaints from users using Internet Explorer (any version - may affect other browsers as well) that the login process proceeds but when redirected they aren't authenticated and are bounced back to loginpage (pages that require authentication check if logged in and if not redirect back to loginpage). Can this be a cookie problem?
Do I need to check if cookies are enabled by the user?
What's the best way to build authentication if you have a custom member table and don't want to use ASP.NET login controls?
Here my current code:
using System;
using System.Linq;
using MyCompany;
using System.Web;
using System.Web.Security;
using MyCompany.DAL;
using MyCompany.Globalization;
using MyCompany.DAL.Logs;
using MyCompany.Logging;
namespace MyCompany
{
public class Auth
{
public class AuthException : Exception
{
public int StatusCode = 0;
public AuthException(string message, int statusCode) : base(message) { StatusCode = statusCode; }
}
public class EmptyEmailException : AuthException
{
public EmptyEmailException() : base(Language.RES_ERROR_LOGIN_CLIENT_EMPTY_EMAIL, 6) { }
}
public class EmptyPasswordException : AuthException
{
public EmptyPasswordException() : base(Language.RES_ERROR_LOGIN_CLIENT_EMPTY_PASSWORD, 7) { }
}
public class WrongEmailException : AuthException
{
public WrongEmailException() : base(Language.RES_ERROR_LOGIN_CLIENT_WRONG_EMAIL, 2) { }
}
public class WrongPasswordException : AuthException
{
public WrongPasswordException() : base(Language.RES_ERROR_LOGIN_CLIENT_WRONG_PASSWORD, 3) { }
}
public class InactiveAccountException : AuthException
{
public InactiveAccountException() : base(Language.RES_ERROR_LOGIN_CLIENT_INACTIVE_ACCOUNT, 5) { }
}
public class EmailNotValidatedException : AuthException
{
public EmailNotValidatedException() : base(Language.RES_ERROR_LOGIN_CLIENT_EMAIL_NOT_VALIDATED, 4) { }
}
private readonly string CLIENT_KEY = "9A751E0D-816F-4A92-9185-559D38661F77";
private readonly string CLIENT_USER_KEY = "0CE2F700-1375-4B0F-8400-06A01CED2658";
public Client Client
{
get
{
if(!IsAuthenticated) return null;
if(HttpContext.Current.Items[CLIENT_KEY]==null)
{
HttpContext.Current.Items[CLIENT_KEY] = ClientMethods.Get<Client>((Guid)ClientId);
}
return (Client)HttpContext.Current.Items[CLIENT_KEY];
}
}
public ClientUser ClientUser
{
get
{
if (!IsAuthenticated) return null;
if (HttpContext.Current.Items[CLIENT_USER_KEY] == null)
{
HttpContext.Current.Items[CLIENT_USER_KEY] = ClientUserMethods.GetByClientId((Guid)ClientId);
}
return (ClientUser)HttpContext.Current.Items[CLIENT_USER_KEY];
}
}
public Boolean IsAuthenticated { get; set; }
public Guid? ClientId {
get
{
if (!IsAuthenticated) return null;
return (Guid)HttpContext.Current.Session["ClientId"];
}
}
public Guid? ClientUserId {
get {
if (!IsAuthenticated) return null;
return ClientUser.Id;
}
}
public int ClientTypeId {
get {
if (!IsAuthenticated) return 0;
return Client.ClientTypeId;
}
}
public Auth()
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
IsAuthenticated = true;
}
}
public void RequireClientOfType(params int[] types)
{
if (!(IsAuthenticated && types.Contains(ClientTypeId)))
{
HttpContext.Current.Response.Redirect((new UrlFactory(false)).GetHomeUrl(), true);
}
}
public void Logout()
{
Logout(true);
}
public void Logout(Boolean redirect)
{
FormsAuthentication.SignOut();
IsAuthenticated = false;
HttpContext.Current.Session["ClientId"] = null;
HttpContext.Current.Items[CLIENT_KEY] = null;
HttpContext.Current.Items[CLIENT_USER_KEY] = null;
if(redirect) HttpContext.Current.Response.Redirect((new UrlFactory(false)).GetHomeUrl(), true);
}
public void Login(string email, string password, bool autoLogin)
{
Logout(false);
email = email.Trim().ToLower();
password = password.Trim();
int status = 1;
LoginAttemptLog log = new LoginAttemptLog { AutoLogin = autoLogin, Email = email, Password = password };
try
{
if (string.IsNullOrEmpty(email)) throw new EmptyEmailException();
if (string.IsNullOrEmpty(password)) throw new EmptyPasswordException();
ClientUser clientUser = ClientUserMethods.GetByEmailExcludingProspects(email);
if (clientUser == null) throw new WrongEmailException();
if (!clientUser.Password.Equals(password)) throw new WrongPasswordException();
Client client = clientUser.Client;
if (!(bool)client.PreRegCheck) throw new EmailNotValidatedException();
if (!(bool)client.Active || client.DeleteFlag.Equals("y")) throw new InactiveAccountException();
FormsAuthentication.SetAuthCookie(client.Id.ToString(), true);
HttpContext.Current.Session["ClientId"] = client.Id;
log.KeyId = client.Id;
log.KeyEntityId = ClientMethods.GetEntityId(client.ClientTypeId);
}
catch (AuthException ax)
{
status = ax.StatusCode;
log.Success = status == 1;
log.Status = status;
}
finally
{
LogRecorder.Record(log);
}
}
}
}
A classic case of over-engineered Authentication mechanism and on top of it the design is bad.
Exceptions should be out of Auth class but reside in same namespace. Can you imagine how .Net framework would look if Microsoft had created exceptions like this. Always keep it simple, stupid (KISS). It seems you need modular code. Try to be simple yet modular.
Your authentication Client-Keys are static magic-values and you're shipping them with your assemblies. Use SecureString instead of readonly string. Anybody can get hold of it using Reflector. How do you sustain change ad security?
Your code directly refers Current HttpContext object when in fact you could have passed the reference of current context object in client-code that will use this.
RequireClientOfType is int[] - why in the world you want to do this ? I believe it could have been an enum or an immutable struct if at all ever needed.
You are already using FormsAuthentication in your Login() and Logout() which is sufficient to replace your entire Auth. Why do you want to re-invent the wheel if ultimately you are going to use FormsAuthnetication to take care of Auth.
And yes if you cannot revise this design please use FxCop/StyleCop at least to avoid spaghetti-code.
Also you could make class Auth as static and expose functionalities like FormsAuthentication does. And also rename it from Auth to Authentication.
This is a prime candidate for http://thedailywtf.com/
Try using built-in asp.net Forms Authentication (Membership).
You can learn from these videos:
Link1 and Link2
If you want to customize it watch this video:
Link

Categories