SimpleMembership in MVC4 app + WebApi using basic HTTP auth - asp.net-mvc-4

I'm trying to implement an MVC4 web application with the following requirements:
(a) it offers its services to authenticated users only. As for authentication, I'd like to use simple membership, as it is the latest authentication technique from MVC, gives me the advantage of defining my own db tables, provides OAuth support out of the box, and is easily integrated with both MVC and WebApi.
(b) it exposes some core functions via WebApi for mobile/JS clients, which should be authenticated via basic HTTP authentication (+SSL). Typically I'll have JS clients using jQuery AJAX calls to WebApi controllers, decorated with the Authorize attribute for different user roles.
(c) ideally, in a mixed environment I would like to avoid a double authentication: i.e. if the user is already authenticated via browser, and is visiting a page implying a JS call to a WebApi controller action, the (a) mechanism should be enough.
Thus, while (a) is covered by the default MVC template, (b) requires basic HTTP authentication without the mediation of a browser. To this end, I should create a DelegatingHandler like the one I found in this post: http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-message-handlers.
The problem is that its implementation requires some way of retrieving an IPrincipal from the received user name and password, and the WebSecurity class does not provide any method for this (except Login, but I would avoid changing the logged user just for the purpose of authorization, also because of potential "mixed" environments like (c)). So it seems my only option is giving up simple membership. Does anyone have better suggestions? Here is the relevant (slightly modified) code from the cited post:
public interface IPrincipalProvider
{
IPrincipal GetPrincipal(string username, string password);
}
public sealed class Credentials
{
public string Username { get; set; }
public string Password { get; set; }
}
public class BasicAuthMessageHandler : DelegatingHandler
{
private const string BasicAuthResponseHeader = "WWW-Authenticate";
private const string BasicAuthResponseHeaderValue = "Basic";
public IPrincipalProvider PrincipalProvider { get; private set; }
public BasicAuthMessageHandler(IPrincipalProvider provider)
{
if (provider == null) throw new ArgumentNullException("provider");
PrincipalProvider = provider;
}
private static Credentials ParseAuthorizationHeader(string sHeader)
{
string[] credentials = Encoding.ASCII.GetString(
Convert.FromBase64String(sHeader)).Split(new[] { ':' });
if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0]) ||
String.IsNullOrEmpty(credentials[1])) return null;
return new Credentials
{
Username = credentials[0],
Password = credentials[1],
};
}
protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
AuthenticationHeaderValue authValue = request.Headers.Authorization;
if (authValue != null && !String.IsNullOrWhiteSpace(authValue.Parameter))
{
Credentials parsedCredentials = ParseAuthorizationHeader(authValue.Parameter);
if (parsedCredentials != null)
{
Thread.CurrentPrincipal = PrincipalProvider
.GetPrincipal(parsedCredentials.Username, parsedCredentials.Password);
}
}
return base.SendAsync(request, cancellationToken)
.ContinueWith(task =>
{
var response = task.Result;
if (response.StatusCode == HttpStatusCode.Unauthorized
&& !response.Headers.Contains(BasicAuthResponseHeader))
{
response.Headers.Add(BasicAuthResponseHeader,
BasicAuthResponseHeaderValue);
}
return response;
});
}
}

Here is another solution that meets all of your requirements. It uses SimpleMemberhsip with a mix of forms authentication and basic authentication in an MVC 4 application. It can also support Authorization, but it is not required by leaving the Role property null.

Thank you, this seems the best available solution at this time!
I managed to create a dummy solution from scratch (find it here: http://sdrv.ms/YpkRcf ), and it seems to work in the following cases:
1) when I try to access an MVC controller restricted action, I am redirected to the login page as expected to get authenticated.
2) when I trigger a jQuery ajax call to a WebApi controller restricted action, the call succeeds (except of course when not using SSL).
Yet, it does not work when after logging in in the website, the API call still requires authentication. Could anyone explain what's going here? In what follows I detail my procedure as I think it might be useful for starters like me.
Thank you (sorry for the formatting of what follows, but I cannot manage to let this editor mark code appropriately...)
Procedure
create a new mvc4 app (basic mvc4 app: this already comes with universal providers. All the universal providers class names start with Default...);
customize web.config for your non-local DB, e.g.:
<connectionStrings>
<add name="DefaultConnection"
providerName="System.Data.SqlClient"
connectionString="data source=(local)\SQLExpress;Initial Catalog=Test;Integrated Security=True;MultipleActiveResultSets=True" />
Also it's often useful to set a machineKey for hashing passwords, so that you can freely move this site around from server to server without having your passwords scrambled. Use a machine key generator website to define an entry like this:
<system.web>
<machineKey
validationKey="...thekey..."
decryptionKey="...thekey..."
validation="SHA1"
decryption="AES" />
if required create a new, empty database corresponding to the connection string of your web.config. Then start our good old pal WSAT (from VS Project menu) and configure security by adding users and roles as required.
if you want to, add a HomeController with an Index action, because no controller is present in this template and thus you could not test-start your web app without it.
add Thinktecture.IdentityModel.45 from NuGet and add/update all your favorite NuGet packages. Notice that at the time of writing this, jquery validation unobtrusive from MS is no more compatible with jQuery 1.9 or higher. I rather use http://plugins.jquery.com/winf.unobtrusive-ajax/ . So, remove jquery.unobtrusive* and add this library (which consists of winf.unobtrusive-ajax and additional-methods) in your bundles (App_Start/BundleConfig.cs).
modify the WebApiConfig.cs in App_Start by adding it the code after the DefaultApi route configuration:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// added for Thinktecture
var authConfig = new AuthenticationConfiguration
{
InheritHostClientIdentity = true,
ClaimsAuthenticationManager = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.ClaimsAuthenticationManager
};
// setup authentication against membership
authConfig.AddBasicAuthentication((userName, password) => Membership.ValidateUser(userName, password));
config.MessageHandlers.Add(new AuthenticationHandler(authConfig));
}
}
To be cleaner, the api controllers will be placed under Controllers/Api/, so create this folder.
Add to models a LoginModel.cs:
public class LoginModel
{
[Required]
[Display(Name = "UserName", ResourceType = typeof(StringResources))]
public string UserName { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password", ResourceType = typeof(StringResources))]
public string Password { get; set; }
[Display(Name = "RememberMe", ResourceType = typeof(StringResources))]
public bool RememberMe { get; set; }
}
This model requires a StringResources.resx resource (with code generation) I usually place under an Assets folder, with the 3 strings quoted in the attributes.
Add a ClaimsTransformer.cs to your solution root, like this:
public class ClaimsTransformer : ClaimsAuthenticationManager
{
public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
{
if (!incomingPrincipal.Identity.IsAuthenticated)
{
return base.Authenticate(resourceName, incomingPrincipal);
}
var name = incomingPrincipal.Identity.Name;
return Principal.Create(
"Custom",
new Claim(ClaimTypes.Name, name + " (transformed)"));
}
}
Add Application_PostAuthenticateRequest to Global.asax.cs:
public class MvcApplication : HttpApplication
{
...
protected void Application_PostAuthenticateRequest()
{
if (ClaimsPrincipal.Current.Identity.IsAuthenticated)
{
var transformer = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.ClaimsAuthenticationManager;
var newPrincipal = transformer.Authenticate(string.Empty, ClaimsPrincipal.Current);
Thread.CurrentPrincipal = newPrincipal;
HttpContext.Current.User = newPrincipal;
}
}
}
web.config (replace YourAppNamespace with your app root namespace):
<configSections>
<section name="system.identityModel"
type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
...
add the other models for account controller, with their views (you can derive them from MVC3 application template, even if I prefer changing them to more localizable-friendly variants using attributes requiring string resource names rather than literals).
to test browser-based authentication, add some [Authorized] action to a controller (e.g. HomeController), and try accessing it.
to test basic HTTP authentication, insert in some view (e.g. Home/Index) a code like this (set your user name and password in the token variable):
...
<p>Test call
$(function() {
$("#test").click(function () {
var token = "USERNAME:PASSWORD";
var hash = $.base64.encode(token);
var header = "Basic " + hash;
console.log(header);
$.ajax({
url: "/api/dummy",
dataType: "json",
beforeSend: function(xhr) {
xhr.setRequestHeader("Authorization", header);
},
success: function(data) {
alert(data);
},
error: function(jqXHR, textStatus, errorThrown) {
alert(errorThrown);
}
});
});
});
This requires the jQuery plugin for encoding/decoding Base64: jquery.base64.js and its minified counterpart.
To allow SSL, follow the instructions here: http://www.hanselman.com/blog/WorkingWithSSLAtDevelopmentTimeIsEasierWithIISExpress.aspx (basically, enable SSL in the web project properties and connect to the port specified in the property value).

Maybe this helps - sounds this is like your scenario:
http://leastprivilege.com/2012/10/23/mixing-mvc-forms-authentication-and-web-api-basic-authentication/
http://leastprivilege.com/2012/10/24/extensions-to-the-web-apimvc-formsbasic-auth-sample-claims-transformation-and-ajax/

Related

Is there a better way of JWT Web Token combined with Windows Auth. for building auth service in an ASP.NET Core project?

The reason behind my question is that, there is a beginner developer team at a company, starting to create a new business project after finishing some vital courses for web applications.
The aim is to have a Web Application within the company's intranet in the following form:
On Angular SPA frontend with ASP.NET Core WebAPI, using Entity Framework Core with a Microsoft SQL Server database running on Windows Server.
The current authentication method of course is Windows Authentication.
In order to create proper auth services, it was suggested to use JWT Web Token, however it is hard to tell whether there is a better approach for using authentication by combining them on the above mentioned Web Application.
As we are lacking of experience, a review of any familiars' would be highly appreciated in this matter!
The current authentication method of course is Windows Authentication.
In order to create proper auth services, it was suggested to use JWT
Web Token.
As you may know JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object.Therefore, JWT creates a JSON web token and encodes, sterilizes, and adds a signature with a secret key that cannot be tampered with; Thus, it would ensure your application security well.
It is hard to tell whether there is a better approach for using
authentication by combining them on the above mentioned Web
Application.
Depending on your current application eco-system you could use Jwt without any concern as you have SPAs and other application running on intranet. While, implementing jwt it would allow you to ensure your authentication regardless of any platform. For instance, It could be windows app, SPA or any cross platform app. You can authenticate all the platfroms using this infrastructure.
As we are lacking of experience, a review of any familiars' would be
highly appreciated in this matter!
Considering your scenario, here is the implementaion steps, you could follow. You always can customize it based on your requirement. Altough, I am share you the basic steps which might assist you.
Note:
Following implementation, can be used either in any internal(intranet) or public web application(internet app) in any platforms.
Implementaion Guideline:
appsettings.json:
"Jwt": {
"Key": "Set_Your_SecretKey",
"Issuer": "YourApplication_URL"
}
Jwt Token View Model:
public class LoginViewModel
{
[Required]
[EmailAddress]
public string Email { get; set; } = string.Empty;
[Required]
[DataType(DataType.Password)]
public string Password { get; set; } = string.Empty;
}
Jwt Token Interface:
public interface IAuthenticationRepository
{
Task<TokenViewModel> AuthenticateLogin(LoginViewModel loginInfo);
}
Repository Implementation:
public class AuthenticationRepository : IAuthenticationRepository
{
private readonly ApplicationDbContext _dbContext;
private readonly IMapper _mapper;
private readonly IConfiguration _config;
public AuthenticationRepository(ApplicationDbContext dbContext, IMapper mapper, IConfiguration config)
{
_dbContext = dbContext;
_mapper = mapper;
_config = config;
}
public async Task<TokenViewModel> AuthenticateLogin(LoginViewModel loginInfo)
{
try
{
var isAuthenticate = await _dbContext.Users.FirstOrDefaultAsync(u => u.UserEmail == loginInfo.Email && u.Password == loginInfo.Password);
var tokenViewModel = new TokenViewModel();
if (isAuthenticate != null)
{
var getToken = GenerateJSONWebToken(loginInfo);
tokenViewModel = _mapper.Map<TokenViewModel>(isAuthenticate);
tokenViewModel.Token = getToken;
}
return tokenViewModel;
}
catch (Exception ex)
{
throw;
}
}
private string GenerateJSONWebToken(LoginViewModel userInfo)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, userInfo.Email),
new Claim(JwtRegisteredClaimNames.Email, userInfo.Password),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var token = new JwtSecurityToken(_config["Jwt:Issuer"],
_config["Jwt:Issuer"],
claims,
expires: DateTime.Now.AddMinutes(120),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
Response Model I Have Used:
public class ResponseViewModel
{
public string output { get; set; }
public string msg { get; set; }
public object apiResponse { get; set; }
}
Auth Controller:
[Route("api/Authentication")]
[ApiController]
public class AuthenticationController : ControllerBase
{
private readonly IAuthenticationRepository _authenticationService;
public AuthenticationController(IAuthenticationRepository authenticationService)
{
this._authenticationService = authenticationService;
}
[AllowAnonymous]
[Route("login")]
[HttpPost]
public async Task<IActionResult> LoginAsync([FromBody] LoginViewModel loginInfo)
{
IActionResult response = Unauthorized();
var user = await _authenticationService.AuthenticateLogin(loginInfo);
if (user.Token != null)
{
response = Ok(new ResponseViewModel { output = "success", msg = "Login Successfully", apiResponse = user });
}
return response;
}
Authenticate Your Access:
Once you have successfully generate jwt auth token, now you can pass that as Bearer token for any authorization and to restrict access you can use [Authorize] before any resource where you wants to restrict access.
Output:
Note: If you would like to know more details on jwt token you could check our official document here

How can I get the user from my AuthorizationAttribute from within an ASPNETCORE 2.2 application?

I have created a custom attribute that I would like to decorate my api controller from within my ASPNETCORE angular application. I am able to set up my authentication as required and log into the application from the login. Then I decorate my api method with my custom attribute.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false)]
public class ManageAuditAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public List<Claim> Claims { get; set; }
public ManageAuditAttribute(String feature)
{
Feature = feature;
}
public bool IsAuthorized()
{
// TODO check there is a claim for the given feature
}
private String Feature { get; }
public void OnAuthorization(AuthorizationFilterContext context)
{
var accessor = (IHttpContextAccessor)context.HttpContext.RequestServices.GetService(typeof(IHttpContextAccessor));
var name = context.HttpContext.User.FindFirst(ClaimTypes.Email); //NULL
var user = context.HttpContext.User; // NULL
var userName = Thread.CurrentPrincipal.Identity.Name; // NULL
}
}
Before saying that claims are not used this way I would add that I need to fit this into a legacy system that has a list of allowed features. I am adding those features to the user as claims and checking the claim exists for each user. The value for the actual claim is the name of the application that the user needs the claim for.
I could easily add these as a custom list to my custom identity user which might be more fitting however, I still need to access my user.
I can get the user but the name is always null and my claims list is empty as well. It is as if I am not logged in at all. Even after logging in.
For retrieving name, you should pass ClaimTypes.Name instead of ClaimTypes.Email like
var user = context.HttpContext.User.FindFirst(ClaimTypes.Name);
Or, you could retrieve by HttpContext.User.Identity.Name like
var userName = context.HttpContext.User.Identity.Name;
I have solved you problem by adding:
app.UseAuthentication();
in Startup.cs.

How can I configure claim authentication in a separate asp.net mvc authentication website with Thinktecture 2 with friendly user login page?

Can anybody tell me what is going wrong with this approach for single sing on?
I have a website A with the authentication logic inside. The user can select the role to access the portal that he wants to go. The problem is how can I configure properly those websites to redirect correctly, because when I redirect, I lose the token(redirect is a GET), I never had the cookie on the portal that I want to go. Do I am missing something in my implementation? Maybe a configuration on the portal that I want to redirect? I am not using the audienceUri on the webconfig, Is this related with the problem? I am using a service that gives me a token if the user is authenticated. Then with that token, I want to redirect the page to the corresponding portal.
Said that I will show you the Login Method in AccountController
[HttpPost]
public async Task<ActionResult> Login(string ddlRoles, string ddlUrls, LoginModel user)
{
var service = new AuthenticationServiceAgent(user.Username, user.Password);
var securityService = new SecurityServiceAgent(service.GetToken());
...processing the claims
//AT THIS POINT THE USER IS AUTHENTICATED
FederatedAuthentication.WSFederationAuthenticationModule.SetPrincipalAndWriteSessionToken(token, true);
.... get the url to redirect
Response.Redirect(urlToRedirect, false);
}
My Proxy class looks like this
public class UserNameTokenServiceProxy : TokenServiceProxy
{
#region Properties
public SecurityCredential Credential { get; set; }
#endregion
#region Methods
public override SecurityToken GetToken(ISecurityCredential credential = null)
{
if (null == credential)
{
throw new ArgumentNullException("credential");
}
var factory = new WSTrustChannelFactory(
new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
new EndpointAddress(StsEndPoint)
);
factory.TrustVersion = TrustVersion.WSTrust13;
if (null != factory.Credentials)
{
factory.Credentials.UserName.UserName = credential.UserName;
factory.Credentials.UserName.Password = credential.Password;
}
var rst = new RequestSecurityToken()
{
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Symmetric,
TokenType = TokenTypes.Saml11TokenProfile11,
***********RelyingPartyUri comes from a config file*********
AppliesTo = new EndpointReference(RelyingPartyUri)
};
try
{
var channel = factory.CreateChannel();
var sToken = channel.Issue(rst);
return sToken;
}
catch (Exception ex)
{
return null;
}
}
#endregion
}
}
And the base class of that one is the following:
public abstract class TokenServiceProxy
{
#region Fields
protected string StsEndPoint;
protected string RelyingPartyUri;
#endregion
#region Constructors
protected TokenServiceProxy()
{
StsEndPoint = ConfigurationManager.AppSettings["stsEndpoint"];
if (null == StsEndPoint)
{
throw new Exception("STSEndPoint cannot be null");
}
RelyingPartyUri = ConfigurationManager.AppSettings["relyingPartyUri"];
if (null == RelyingPartyUri)
{
throw new Exception("RelyingPartyUri cannot be null");
}
//StsEndPoint = <add key="stsEndpoint" value="https://sso.dev.MyCompany.com/idsrv/issue/wstrust/mixed/username"/>;
//RelyingPartyUri = #"value="https://dev.MyCompany.com/MyCompanyPortal"/>";
}
#endregion
#region Abstract Methods
public abstract SecurityToken GetToken(ISecurityCredential credential = null);
#endregion
}
Basically we are not using the default configuration for WS with the audienceUri and the federationConfiguration section, the equivalent would be:
<system.identityModel.services>
<federationConfiguration>
<cookieHandler mode="Default" requireSsl="false" />
<wsFederation passiveRedirectEnabled="true" issuer="https://sso.dev.MyCompany.com/idsrv/issue/wstrust/mixed/username" realm="https://dev.MyCompany.com/MyCompanyPortal" requireHttps="false" />
</federationConfiguration>
</system.identityModel.services>
I'm not quite sure what are all the components in your solution, but in any case you seem to try to do too much on your own.
SSO or not - each integrated application needs to maintain its own authentication state (with authentication cookie), and to do so it needs to get the security token from Identity Provider.
In a most common scenario for WS-Federation (Relying Party initiated SSO), Relying Party application redirects user to Identity Provider, Identity Provider checks user credentials and POSTS security token back to Relying Party application, where it is handled by WS-Federation module to create authentication cookie.
In your case I guess that you are trying to achieve "Identity Provider initiated" SSO (assuming, that your login page is part of Identity Provider).
If this is the case, you need to POST created security token to selected Relying Party (as far as I know WS-Federation does not support sending tokens in GET query string).
Of course you need to have proper WS-Federation configuration on your Relying Party side to accept this token and create authentication cookie - having that the authentication is done automatically by WS-Federation module.

How are bearer tokens stored server-side in Web API 2?

I am setting up bearer token authentication in Web API 2, and I don't understand how (or where) the bearer token is being stored server-side. Here is the relevant code:
Startup:
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static Func<UserManager<IdentityUser>> UserManagerFactory { get; set; }
public static string PublicClientId { get; private set; }
static Startup()
{
PublicClientId = "self";
UserManagerFactory = () => new UserManager<IdentityUser>(new UserStore<IdentityUser>());
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
}
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions());
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseOAuthBearerTokens(OAuthOptions);
}
}
WebApiConfig:
public class WebApiConfig
{
public static void ConfigureWebApi()
{
Register(GlobalConfiguration.Configuration);
}
public static void Register(HttpConfiguration http)
{
AuthUtil.ConfigureWebApiToUseOnlyBearerTokenAuthentication(http);
http.Routes.MapHttpRoute("ActionApi", "api/{controller}/{action}", new {action = Actions.Default});
}
}
AuthUtil:
public class AuthUtil
{
public static string Token(string email)
{
var identity = new ClaimsIdentity(Startup.OAuthOptions.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, email));
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
var currentUtc = new SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromMinutes(30));
var token = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
return token;
}
public static void ConfigureWebApiToUseOnlyBearerTokenAuthentication(HttpConfiguration http)
{
http.SuppressDefaultHostAuthentication();
http.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
}
}
LoginController:
public class LoginController : ApiController
{
...
public HttpResponseMessage Post([FromBody] LoginJson loginJson)
{
HttpResponseMessage loginResponse;
if (/* is valid login */)
{
var accessToken = AuthUtil.Token(loginJson.email);
loginResponse = /* HTTP response including accessToken */;
}
else
{
loginResponse = /* HTTP response with error */;
}
return loginResponse;
}
}
Using the above code, I'm able to login and store the bearer token client-side in a cookie, and then make calls to controllers marked with [Authorize] and it lets me in.
My questions are:
Where / how is the bearer token being stored server-side? It seems like this is hapenning through one of the OWIN calls but I can't tell where.
Is it possible to persist the bearer tokens to a database server-side so that they can remain in place after a Web API server restart?
If the answer to #2 is no, is there anyway for a client to maintain its bearer token and re-use it even after the Web API goes down and comes back up? While this may be rare in Production, it can happen quite often doing local testing.
They're not stored server side -- they're issued to the client and the client presents them on each call. They're verified because they're signed by the owin host's protection key. In SystemWeb hosting, that protection key is the machineKey setting from web.config.
That's unnecessary, as long as the protection key the owin host uses doesn't change across server restarts.
A client can hold onto a token for as long as the token is valid.
For those who are looking for how to set web.config, here is a sample
<system.web>
<machineKey validation="HMACSHA256" validationKey="64-hex"
decryption="AES" decryptionKey="another-64-hex"/>
</system.web>
You need both validationKey and decriptionkey to make it work.
And here is how to generate keys
https://msdn.microsoft.com/en-us/library/ms998288.aspx
To add to this, the token can be persisted server side using the SessionStore property of of CookieAuthenticationOptions. I wouldn't advocate doing this but it's there if your tokens become excessively large.
This is an IAuthenticationSessionStore so you could implement your own storage medium.
By default the token is not stored by the server. Only your client has it and is sending it through the authorization header to the server.
If you used the default template provided by Visual Studio, in the Startup ConfigureAuth method the following IAppBuilder extension is called: app.UseOAuthBearerTokens(OAuthOptions).

Adding RequestFilter data to Context (Request Scope), Retrieve in Service

I implemented Basic Auth for my services. Since ServiceStack's AuthFeature is strongly coupled with the session concept, I implemented a custom RequestFilter that performs stateless basic auth (credentials go in, on every request). Our auth strategy internally contemplates roles and permissions.
Besides authentication, we need to enforce authorization (e.g., user is manipulating a product that he owns). We are using FluentValidation, for all service validations.
Authorization validations include cross checking auth data with request parameters. Question is, where should I place the auth data produced in the BasicAuthRequestFilter? Should I key value pair it in cache, associating, for instance, RequestContext (or any other object that uniquely identifies the request scope) with an Authentication object?
I could just plug the AuthData in the Request Dto, which is available directly at the RequestFilter, however this would mess up our service contract design. We define dtos in a separate DLL, where only service input/output details are defined.
Any suggestions?
Thanks in advance.
I too use my own custom authentication mechanism and make custom role information available to my service. I do this by authenticating the request in a custom ServiceRunner which can then pass the information directly to my custom Service base. This ultimately means accessing information about the users permissions is exceptionally easy.
Create a custom ServiceRunner:
public class ServiceRunner<T> : ServiceStack.ServiceHost.ServiceRunner<T>
{
public ServiceRunner(IAppHost appHost, ActionContext actionContext) : base(appHost, actionContext)
{
}
public override object Execute(IRequestContext requestContext, object instance, T request)
{
// Check if the instance is of type AuthenticatedBase
var authenticatedBase = instance as AuthenticatedBase;
// If the request is not using the AuthenticatedBase, then allow it to run, as normal.
if(authenticatedBase == null)
return base.Execute(requestContext, instance, request);
/*
* Authentication required. Do you authorization check here.
* i.e.
* var authorization = requestContext.GetHeader("Authorization");
* bool authorised = ... some condition;
*/
/* You have access to your service base so if you injected the Db connection
* in you app config using IoC, then you can access the Db here.
* i.e.
* authenticatedBase.Db
*/
/*
* Not authorized?
* throw new UnauthorizedException();
*/
/*
* If authorized:
* Then simple set the details about their permissions
*/
authenticatedBase.AuthData = new AuthData { Id = 123, Roles = [], Username = "" };
// Pass back the authenticated base
return base.Execute(requestContext, authenticatedBase, request);
}
}
Configure you application to use it by adding this to your AppHost:
public override IServiceRunner<TRequest> CreateServiceRunner<TRequest>(ActionContext actionContext)
{
return new ServiceRunner<TRequest>(this, actionContext);
}
Create a custom class to hold your auth data i.e. the user session information, such as:
public class AuthData
{
public int Id { get; set; }
public string Username { get; set; }
public int[] Roles { get; set; }
...
}
Then create a custom service base
public class AuthenticatedBase : Service
{
public AuthData AuthData { get; set; }
}
To then use the AuthData in the service is simply a case of extending AuthenticatedBase.
public class CustomerHandler : AuthenticatedBase
{
public object Get(ListCustomers request)
{
// You can access the AuthData now in the handler
var roles = AuthData.Role; // Check they have the role required to list customers
...
}
}
You are probably wondering why go to all the trouble of using the ServiceRunner over a RequestFilter but the main advantage is it gives direct access to the instance of the Service base, which isn't available to a RequestFilter.
The RequestFilters are run before the Service base is instantiated, so you can't populate it from there. See order of operations for more information.
By having access to the ServiceBase we can populate values (in this case AuthData) and we have access to our injected dependancies such as the database connection.
I hope you find this useful. You should be able to copy most of your existing RequestFilter into the service runner. If you need any further help with this just let me know.
Update to support Attributes:
Since you are unable to avoid using the attribute method to handle your authentication needs you can still use this method:
Continue doing your authentication and access filtering the way you were before.
In your existing authentication mechanism use req.Items.Add to set the AuthData i.e. Where req is your request object
req.Items.Add("AuthData", new AuthData { Username = "", Roles = [] ... });
Then access your AuthData item in your service base:
public class AuthenticatedBase : Service
{
public AuthData AuthData
{
get { return base.Request.Items["AuthData"] as AuthData; }
}
}