ASP.NET Core 1.0 OAuth Server using Openiddict - oauth

I would like to use Openiddict OAuth to protect api endpoints in my ASP.NET Core 1.0 Web Application. The api endpoints will be called by a phone app and users must login with username and password.
The flow goes like this:
User can register and login via web application: https://www.domain.com
User install phone app, and they can login and register using the phone app. Login, registration and data access is done via api endpoints: Example: https://www.domain.com/api/service/getsomedata
How can I configure Openiddict OAuth so I can protect the API endpoints using OAuth?

How can I configure Openiddict OAuth so I can protect the API endpoints using OAuth?
Your scenario sounds like a good candidate for the simple "resource owner password credentials" grant, which is basically the OAuth2 equivalent of basic or forms authentication.
Here's what I'd recommend:
Create a new AccountController/RegistrationController API controller responsible of creating new accounts:
Since the user account doesn't exist at this stage, you can't use token authentication here (just like the default AccountController.Register template cannot require cookies authentication before the user is registered).
Configure OpenIddict to enable the token endpoint and allow the resource owner password credentials grant:
services.AddOpenIddict<ApplicationDbContext>()
// Disable the HTTPS requirement during development.
.DisableHttpsRequirement()
// Enable the token endpoint, required to use
// the resource owner password credentials grant.
.EnableTokenEndpoint("/connect/token")
// Enable the password and the refresh token flows.
.AllowPasswordFlow()
.AllowRefreshTokenFlow();
Use the OAuth2 validation middleware to protect your APIs:
To enable token authentication, reference AspNet.Security.OAuth.Validation 1.0.0-alpha2-final package and add app.UseOAuthValidation() before app.UseMvc(). To make authentication mandatory, simply use the [Authorize] attribute like you'd do with cookies authentication.
Don't hesitate to play with this sample. It doesn't use a mobile app for the client-side part, but you should easily understand how it works.
For more information, you can also read this blog post, written by Mike Rousos for the Microsoft .NET Web Development and Tools blog: Bearer Token Authentication in ASP.NET Core

Ok, Thanks #Pinpoint for pointing me to the right direction.
However here is my Startup.cs configuration:
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
if (env.IsDevelopment())
{
// For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709
builder.AddUserSecrets();
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddOpenIddict<ApplicationUser, ApplicationRole, ApplicationDbContext>()
.DisableHttpsRequirement()
.EnableTokenEndpoint("/connect/token")
.AllowPasswordFlow()
.AllowRefreshTokenFlow()
.UseJsonWebTokens();
services.AddMvc();
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseIdentity();
app.UseOpenIddict();
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
RequireHttpsMetadata = false,
Audience = "http://localhost:24624/",
Authority = "http://localhost:24624/"
});
// Add external authentication middleware below. To configure them please see http://go.microsoft.com/fwlink/?LinkID=532715
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
ApplicationDbContext.cs:
public class ApplicationDbContext : OpenIddictDbContext<ApplicationUser, ApplicationRole>
{
public ApplicationDbContext(DbContextOptions options)
: base(options)
{
Database.EnsureCreated();
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
}
ApplicationRole.cs:
public class ApplicationRole : IdentityRole
{
}
ApplicationUser.cs:
public class ApplicationUser : OpenIddictUser
{
}
ServiceController.cs:
[Authorize(ActiveAuthenticationSchemes = OAuthValidationDefaults.AuthenticationScheme)]
[Route("api/service")]
public class ServiceController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
public ServiceController(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
[HttpGet]
[Route("getdata")]
public async Task<IActionResult> GetData()
{
var user = await _userManager.GetUserAsync(User);
if (user == null) return Ok("No user / not logged in");// if Authorize is not applied
return Ok(user);
}
}
The key in here is the ServiceController.cs: [Authorize(ActiveAuthenticationSchemes = OAuthValidationDefaults.AuthenticationScheme)]
#Pinpoint: I didn't use app.UseOAuthValidation() because it was returning 302 and redirect to Account/Login.
So now it works like this:
accessing the http://domain.com, user can register, login, see data, etc.
user can download mobile app, register, login and get data
Implementing the user registration login in the api side is preatty easy and straight forward.
The problem was that using fiddler and issuing a GET to http://domain.com/api/service/getdata was returning a 302 and redirect to Account/Login. If I remove app.UseIdentity(), then if will have returned 401 Unauthorized but user would have not been able to login anymore using the UI http://domain.com. Adding this [Authorize(ActiveAuthenticationSchemes = OAuthValidationDefaults.AuthenticationScheme)] to my ServiceController solved the problem.
#Pinpoint what was the benefit of app.UseOAuthValidation() ?

Related

How to put ASP.net Identity Roles into the Identityserver4 Identity token

Although I am very happy that IdentityServer4 exists and makes life I with regards to authentication much easier in many ways, I've stumbled onto the problem and the many discussions of adding roles to claims within the community.
My requirements are simple:
I develop apps (xamarin forms) who require authentication and authorization
I need a system to store the identity of my users (name, pwd, roles, phone…) -> ASP.net Identity
I need a system to authenticate my users -> IdentityServer 4
Roles per user are very limited (user/admin) and don’t change
I need an API backend and an MVC admin site (asp.net core)
I want to limit access to some API/MVC controllers using [Authorize(Roles = "Admin")]
I've spent countless hours trying different configurations to get asp.net Identity roles to be passed to my MVC application after authentication but without luck. The purpose is as described in point 6.
I've also spent countless hours reading but I have a feeling that the implementation IdentityServer4.AspIdentity regarding roles has changed a lot since the version v1.0.0.
After reading a lot about this, it remains unclear how to actually implement this as it seems that some of the solution described only 2 months ago are no longer valid.
So, for now I believe there are 2 paths:
Role into the identity token by only requesting an identity token
and using AlwaysIncludeInIdToken?
Let the client retrieve the roles
using the userinfo endpoint and somehow inject them into the
httpcontext(?) alowing mvc to check using [Authorize(Roles =
"Admin")]?
Anyway, that's my assumptions.
So, please help out and explain/document in detail so we can start implementing this in a durable way? Some workable examples would be great as well.
So, after investigation I’ve come up with 2 methods to do this:
Include Roles/other claims on the Identity Server Side
Include Roles/other claims on the Client side
Include on Identity Server Side
ravi punjwani provided the answer in ‘How to add additional claims to be included in the access_token using ASP.Net Identity with IdentityServer4. His solution is still in draft but the solution allows you to add any claim before the token is send back to the client. This is the link: How to add additional claims to be included in the access_token using ASP.Net Identity with IdentityServer4
Include on Client side
This one is a little tougher as it involves adding ‘IClaimsTransformer’ in the request pipeline of the client. The result is that per request, the Claimstransformer will retrieve the claims for the user and add it to the User Identity claims (the token). The set-up of the Claimstransformer is not easy as it’s tricky to get DI to work but after lots of research the celow solution does it for me.
The Custom ClaimsTransformer class does the transformation in the Middleware:
public class KarekeClaimsTransformer : IClaimsTransformer
{
private readonly UserManager _userManager;
public KarekeClaimsTransformer(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
{
if (context.Principal.Identity.IsAuthenticated)
{
Claim userId = context.Principal.FindFirst("sub");
if (context.Principal.FindFirst("role") == null && userId != null)
{
ApplicationUser user = await _userManager.FindByIdAsync(userId.Value);
var roles = await _userManager.GetRolesAsync(user);
foreach (var role in roles)
{
((ClaimsIdentity)context.Principal.Identity).AddClaim(new Claim(JwtClaimTypes.Role, role,
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role"));
}
}
}
return Task.FromResult(context.Principal).Result;
}
}
In the Client start-up class you need to Add it to the scope in ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
...
services.AddScoped<IClaimsTransformer, KarekeClaimsTransformer>();
// Add framework services.
services.AddMvc();
}
Lastly, add in Configure:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "http://localhost:5000",
RequireHttpsMetadata = false,
ApiName = "api1"
});
app.UseClaimsTransformation((context) =>
{
IClaimsTransformer transformer = context.Context.RequestServices.GetRequiredService<IClaimsTransformer>();
return transformer.TransformAsync(context);
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "api/{controller}/{action?}/{id?}");
});
}

How to integrate IdentityServer v3/v4 into MVC to manage user Roles and Claims?

In ASP.NET 2005 (v2 timeframe) there was a web-based tool called ASP.NET Website Administration tool that folks could use the edit users and generally administer the ASP.NET Membership database. This useful tool was removed in 2012 and is still missed.
http://www.hanselman.com/blog/ThinktectureIdentityManagerAsAReplacementForTheASPNETWebSiteAdministrationTool.aspx
edited- To integrate custom roles into my MVC application the right version was not server, need to use the IdentityManager
https://github.com/IdentityManager/IdentityManager.AspNetIdentity
Compile the solution. In Web.config modify the to the working SQL database. In my case I already had some aspIdentity tables that had to be deleted so
Entity could create new ones. Now this identity manager code should run and work to create users, set Roles and Claims and save to the Table.
Now the goal is to match up the database Table and the Authentication scheme, so that some other new MVC project will look for its Roles here. The
IdentityManager software, for now, will be a utility to set the roles.
In the MVC application go to Tools, NuGet, look for 'identitymanager', there should be 3 beta files. Get the identitymanager and the aspIdentity.
Project will also need Owin (but I had this installed already). Modify Startup.cs:
Public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
app.Map("/idm", idm =>
{
var factory = new IdentityManagerServiceFactory();
factory.IdentityManagerService = new Registration<IIdentityManagerService, ApplicationIdentityManagerService>();
factory.Register(new IdentityManager.Configuration.Registration<ApplicationUserManager>());
factory.Register(new IdentityManager.Configuration.Registration<ApplicationUserStore>());
factory.Register(new IdentityManager.Configuration.Registration<ApplicationDbContext>());
factory.Register(new IdentityManager.Configuration.Registration<ApplicationRoleManager>());
factory.Register(new IdentityManager.Configuration.Registration<ApplicationRoleStore>());
idm.UseIdentityManager(new IdentityManagerOptions
{
Factory = factory
});
});
}
}
And create these classes,
public class ApplicationUserStore : UserStore<ApplicationUser>
{
public ApplicationUserStore(ApplicationDbContext ctx)
: base(ctx)
{
}
}
// public class ApplicationRole :
public class ApplicationRoleStore : RoleStore<IdentityRole>
{
public ApplicationRoleStore(ApplicationDbContext ctx)
: base(ctx)
{
}
}
public class ApplicationRoleManager : RoleManager<IdentityRole>
{
public ApplicationRoleManager(ApplicationRoleStore roleStore)
: base(roleStore)
{
}
}
public class ApplicationIdentityManagerService : AspNetIdentityManagerService<ApplicationUser, string, IdentityRole, string>
{
public ApplicationIdentityManagerService(ApplicationUserManager userMgr, ApplicationRoleManager roleMgr)
: base(userMgr, roleMgr)
{
}
}
Then in IdentityConfig.cs modify the ApplicationUserManager class
// Configure the application user manager used in this application. UserManager is defined in ASP.NET Identity and is used by the application.
public class ApplicationUserManager : UserManager<ApplicationUser>
{
// public ApplicationUserManager(IUserStore<ApplicationUser> store)
public ApplicationUserManager(ApplicationUserStore store)
: base(store)
{
}
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
// var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
var manager = new ApplicationUserManager(new ApplicationUserStore(context.Get<ApplicationDbContext>()));
The ConfigureAuth method:
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
// Enables the application to remember the second login verification factor such as phone or email.
// Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
// This is similar to the RememberMe option when you log in.
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
// Uncomment the following lines to enable logging in with third party login providers
//app.UseMicrosoftAccountAuthentication(
// clientId: "",
// clientSecret: "");
//app.UseTwitterAuthentication(
// consumerKey: "",
// consumerSecret: "");
//app.UseFacebookAuthentication(
// appId: "",
// appSecret: "");
//app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
//{
// ClientId = "",
// ClientSecret = ""
//});
}
At this point, the utility points to the same SQL path as the running MVC application. The "Authentication" should be shared and should work in the
MVC app. If I created my user account, created a Role called Finance? Then go back and edit the user, and add the new Role called Finance,
and in the MVC controller put:
[Authorize(Roles ="Finance")]
The Role was made by the utility, stored in the SQL, then my MVC is hopefully going to see, use, get or apply this Role and only let my user account be
authorized.
Right now, it will not authorize, and sends the browser back to the login, have to assume its because of a failed authorization.
So close but what could make this not work?
Identity Server (the OpenID Connect Provider) and Identity Manager (the identity management tool you are after) dropped the Thinktecture prefix sometime in 2015. You may be using out of date nuget packages as a result.
Also, Identity Server 4 uses .NET Core, Identity Server 3 and Identity Manager use the .NET Framework.
If you are looking for an up to date guide of getting started with Identity Manager, I have a walkthrough on my blog release earlier this year: https://www.scottbrady91.com/ASPNET-Identity/Identity-Manager-using-ASPNET-Identity

Keep using cookies for authentication in Web API with OWIN and app.UseWebApi

I'd like to keep using same cookies in MVC and API parts of my app. I know this isn't very secure but still.
Everything works if I create a new MVC project in VS, Web API is set up from Global.asax using GlobalConfiguration.Configure(WebApiConfig.Register).
But as soon as I'm trying to use OWIN to configure Web API I run into a problem where User is always null in my API controllers.
Here's my code from Startup.cs:
var config = new HttpConfiguration();
WebApiConfig.Register(config);
app.UseWebApi(config);
Controllers work, routes too, same WebApiConfig.cs file is used. However the User is null in my API controllers now. What's missing from my instance of HttpConfiguration that is present in GlobalConfiguration.Configuration?
I need to use my own instance of HttpConfiguration instead of using GlobalConfiguration.Configuration because I'm planning to use Autofac and it doesn't work with GlobalConfiguration as mentioned here
EDIT:
My Startup.Auth.cs:
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
What's missing from my instance of HttpConfiguration that is present
in GlobalConfiguration.Configuration?
GlobalConfiguration.cs Source code from Codeplex
The main difference between when you create a new HttpConfiguration and the one In GlobalConfiguration...
public static class GlobalConfiguration
{
private static Lazy<HttpConfiguration> _configuration = CreateConfiguration();
//...other code removed for brevity
/// <summary>
/// Gets the global <see cref="T:System.Web.Http.HttpConfiguration"/>.
/// </summary>
public static HttpConfiguration Configuration
{
get { return _configuration.Value; }
}
//...other code removed for brevity
private static Lazy<HttpConfiguration> CreateConfiguration()
{
return new Lazy<HttpConfiguration>(() =>
{
HttpConfiguration config = new HttpConfiguration(new HostedHttpRouteCollection(RouteTable.Routes));
ServicesContainer services = config.Services;
Contract.Assert(services != null);
services.Replace(typeof(IAssembliesResolver), new WebHostAssembliesResolver());
services.Replace(typeof(IHttpControllerTypeResolver), new WebHostHttpControllerTypeResolver());
services.Replace(typeof(IHostBufferPolicySelector), new WebHostBufferPolicySelector());
services.Replace(typeof(IExceptionHandler),
new WebHostExceptionHandler(services.GetExceptionHandler()));
return config;
});
}
//...other code removed for brevity
}
Also when looking at how the UseWebAPi extension in
WebApiAppBuilderExtensions.cs
public static IAppBuilder UseWebApi(this IAppBuilder builder, HttpConfiguration configuration)
{
if (builder == null)
{
throw new ArgumentNullException("builder");
}
if (configuration == null)
{
throw new ArgumentNullException("configuration");
}
HttpServer server = new HttpServer(configuration);
try
{
HttpMessageHandlerOptions options = CreateOptions(builder, server, configuration);
return UseMessageHandler(builder, options);
}
catch
{
server.Dispose();
throw;
}
}
...the configuration is wrapped in its own HttpServer which overrides the default one used by GlobalConfiguration.
Looking through the documentation you included, I eventually came across this
For standard IIS hosting, the HttpConfiguration is
GlobalConfiguration.Configuration.
For self hosting, the HttpConfiguration is your
HttpSelfHostConfiguration instance.
For OWIN integration, the HttpConfiguration is the one you create in
your app startup class and pass to the Web API middleware.
With standard IIS hosting, IIS handles user Authentication and Identification which it plugs into the HttpConfiguration and pipeline under the hood for you. When you new up HttpConfiguration your self you don't have the benefits of IIS to manage Authentication for you so your User remains null.
From your post you indicate that you are using more than one instance of HttpConfiguration which looks like you are trying to mix IIS and OWIN.
Looking at this question : OWIN Cookie Authentication
The answer shows that in the WebApi Config the following line was ignoring the cookie.
// Configure Web API to use only bearer token authentication.
// If you don't want the OWIN authentication to flow to your Web API then call
// SuppressDefaultHostAuthentication on your HttpConfiguration.
// This blocks all host level authentication at that point in the pipeline.
config.SuppressDefaultHostAuthentication();
Commenting it out made the cookie based Authentication work.
UPDATE:
You indicated...
Controllers work, routes too, same WebApiConfig.cs file is used.
However the User is null in my API controllers now
Take a look at...
Combining Authentication Filters with Host-Level Authentication
“Host-level authentication” is authentication performed by the host
(such as IIS), before the request reaches the Web API framework.
Often, you may want to to enable host-level authentication for the
rest of your application, but disable it for your Web API controllers.
For example, a typical scenario is to enable Forms Authentication at
the host level, but use token-based authentication for Web API.
To disable host-level authentication inside the Web API pipeline, call
config.SuppressHostPrincipal() in your configuration. This causes
Web API to remove the IPrincipal from any request that enters the
Web API pipeline. Effectively, it "un-authenticates" the request.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.SuppressHostPrincipal();
// Other configuration code not shown...
}
}
If in your scenario you have the following in your web api configuration, it would explain why your User is always null. I suggest you comment it out or remove it all together.
I had exactly this problem when I transferred to OWIN from a WebApi only service. My user was also null even though it was correctly authenticated. In my case I had missed adding the HostAuthenticationFilter in after suppressing the Default Host Authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(YourAuthenticationType));
Check that you have not forgotten your authentication filter here. I've done this successfully using Unity (rather than Autofac) but the principal is exactly the same. In my OWIN startup this order:
ConfigureAuth(app);
WebApiConfig.Register(httpConfiguration);
app.UseWebApi(httpConfiguration);

Authentication with ASP.NET MVC site and ServiceStack API

I am developing a website using ASP.NET MVC with an API using ServiceStack.
Very soon I want to add authentication. The website will have at least two types of users 'service providers' and 'service consumers', although a user could have multiple roles.
I am open to using new MVC Identity, but I want whatever I use to work nicely for both the servicestack API and MVC 'pages' that don't necessarily use the API but should show different content based on login. I do not want to require javascript for login/logout.
I would like the solution to use tokens as I have not used session state anywhere else, but I am open to other options providing they would scale horizontally on a cloud provider (users next request may go to a different instance of back-end).
Anyone have example of an ideal solution?
(N.B: I am not interested in an externally hosted service).
ServiceStack's Authentication can also be used by external ASP.NET Web Frameworks, the ServiceStack and MVC Integration docs shows how you can accept Login credentials from a MVC Controller and register them with ServiceStack:
public ActionResult Login(string userName, string password, string redirect=null)
{
if (ModelState.IsValid)
{
try
{
using (var authService = ResolveService<AuthenticateService>())
{
var response = authService.Authenticate(new Authenticate {
provider = CredentialsAuthProvider.Name,
UserName = userName,
Password = password,
RememberMe = true,
});
// add ASP.NET auth cookie
FormsAuthentication.SetAuthCookie(userName, true);
return Redirect(string.IsNullOrEmpty(redirect) ? "/" : redirect);
}
}
catch (Exception ex)
{
ModelState.AddModelError(string.Empty, ex.Message);
}
}
return View("Index", GetViewModel());
}
The http://mvc.servicestack.net Live Demo shows an example of calling this controller using a standard MVC HTML Form.
Your MVC Controllers can then inherit ServiceStackController to access the Authenticated Users Session and different ServiceStack providers, here are the API's relating to Session and Authentication:
public class ServiceStackController : Controller
{
//...
ISession SessionBag { get; set; }
bool IsAuthenticated { get; set; }
IAuthSession GetSession(bool reload = true);
TUserSession SessionAs<TUserSession>();
void ClearSession();
}
Enable OAuth Providers
Should you need to you can also enable ServiceStack's different OAuth providers which can optionally callback either directly to a ServiceStack Service or
Further to mythz answer I also needed to know if a user was authenticated in a view and the normal Request.IsAuthenticated does not work when your doing above. So I created a CustomWebViewPage (to use this you will have to change *pageBaseType="Your.NameSpace.CustomWebViewPage" in the View folder's Web.config).
public abstract class CustomWebViewPage : WebViewPage
{
private IServiceStackProvider _serviceStackProvider;
public virtual IServiceStackProvider ServiceStackProvider
{
get
{
return _serviceStackProvider ?? (_serviceStackProvider =
new ServiceStackProvider(new AspNetRequest(base.Context, GetType().Name)));
}
}
public virtual bool IsAuthenticated
{
get { return ServiceStackProvider.IsAuthenticated; }
}
}
public abstract class CustomWebViewPage<TModel> : WebViewPage<TModel>
{
//EXACTLY the same as above method...

Using bearer tokens and cookie authentication together

I have a single page app - more or less based on the MVC5 SPA template - using bearer tokens for authentication.
The site also has a couple of conventional MVC pages which need to be secured, but using cookie authentication.
In Startup.Auth I can enable both types of authorisation:
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOAuthBearerTokens(OAuthOptions);
However, this seems to have a side-effect in that whenever an AJAX request is sent from the SPA, it sends both the bearer token in the header and the cookie.
Whereas the behaviour I really want is that only the bearer token is used for WebAPI calls, and only the cookie for MVC calls.
I'd also like the MVC calls to redirect to a login page when not authorised (set as a CookieAuthenticationOption), but obviously I don't want this to happen when making an API call.
Is there some way to have this type of mixed-mode authentication within one application? Perhaps through a path/route filter?
I think I worked this out:-
Startup.Auth is wiring up the OWIN pipeline, so it is right to include Cookies and Tokens there. But one change to the cookie options specifies the authentication type it should apply to:
CookieOptions = new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie
};
Then I needed to configure WebAPI to only use tokens:
public static void Configure(HttpConfiguration config)
{
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
}
This seems to achieve what I want. WebAPI just uses bearer tokens and no cookies, and a few conventional MVC pages use cookies once logged in (using the AuthenticationManager).
you can add jwt token to cookie (here my jwt token cookie name is "access_token") in http-only mode,and make a middleware like this
public class JwtCookieMiddleware
{
private readonly RequestDelegate _next;
public JwtCookieMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext ctx)
{
if (ctx.Request.Cookies.TryGetValue("access_token", out var accessToken))
{
if (!string.IsNullOrEmpty(accessToken))
{
string bearerToken = String.Format("Bearer {0}", accessToken);
ctx.Request.Headers.Add("Authorization",bearerToken);
}
}
return this._next(ctx);
}
}
public static class JwtCookieMiddlewareExtensions
{
public static IApplicationBuilder UseJwtCookie(this IApplicationBuilder build)
{
return build.UseMiddleware<JwtCookieMiddleware>();
}
}
And you need use the middleware in startup like this:
app.UseJwtCookie();
app.UseAuthentication();
app.UseMvc();
the above code will add jwt token to http request header if this request with a token cookie;

Resources