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

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

Related

How to use Auth0 for full signup-signin process

I am currently having troubles using auth0.com to set up whole authentication process for my asp.net web api project (I didn't write any view part, cause I'm using it only to learn auth/authoriz).
Their quickstart guides and docs are starting from obtaining a token for your application, I don't understand what is this token, does it grants an access to whole application or what? I wrote a default implementation with creating a user as an object, then generating a token and assigning it to user, then you pass user's email and password and log. I want to do the same using auth0.com
Is there a COMPLETE step-by-step guide on using auth0.com, with the explanation on how to create a user, how to let user log in etc.?
My default implementation:
private readonly UserManager<AppUser> _userManager;
private readonly TokenService _tokenService;
public AccountController(UserManager<AppUser> userManager, TokenService tokenService)
{
_tokenService = tokenService;
_userManager = userManager;
}
[AllowAnonymous]
[HttpPost("login")]
public async Task<ActionResult<UserDTO>> Login(LoginDTO loginDTO)
{
var user = await _userManager.FindByEmailAsync(loginDTO.Email);
if (user is null) return Unauthorized();
var result = await _userManager.CheckPasswordAsync(user, loginDTO.Password);
if (result)
{
return CreateUserObject(user);
}
return Unauthorized();
}
[AllowAnonymous]
[HttpPost("register")]
public async Task<ActionResult<UserDTO>> Register(RegisterDTO registerDTO)
{
if (await _userManager.Users.AnyAsync(x => x.UserName == registerDTO.Username))
{
ModelState.AddModelError("username", "Username taken");
return ValidationProblem();
}
if (await _userManager.Users.AnyAsync(x => x.Email == registerDTO.Email))
{
ModelState.AddModelError("email", "Email taken");
return ValidationProblem();
}
var user = new AppUser
{
DisplayName = registerDTO.DisplayName,
Email = registerDTO.Email,
UserName = registerDTO.Username
};
var result = await _userManager.CreateAsync(user, registerDTO.Password);
if (result.Succeeded)
{
return CreateUserObject(user);
}
return BadRequest(result.Errors);
}
[Authorize]
[HttpGet]
public async Task<ActionResult<UserDTO>> GetCurrentUser()
{
var user = await _userManager.FindByEmailAsync(User.FindFirstValue(ClaimTypes.Email));
return CreateUserObject(user);
}
private UserDTO CreateUserObject(AppUser user)
{
return new UserDTO
{
DisplayName = user.DisplayName,
Image = null,
Token = _tokenService.CreateToken(user),
Username = user.UserName
};
}
In general, you don't need to set up a sign-in/sign-up infrastructure with Auth0. The platform provides you with the Universal Login page where users can register or log in to your application.
The result of the authentication on the Auth0 side is one or two tokens that tell you some info about the user (ID token) and optionally what the user/application is allowed to do (access token).
To learn more about these tokens and the difference between them, read this article.
In your case, since your application is an API, you don't have to deal with user authentication directly. Your API isn't meant for users but for client applications.
To manage users, you can do it through the Auth0 Dashboard. If you want to create your own dashboard to manage users, you can do it through the Auth0 Management API. This is the library to use for .NET.
You assume that a client will call your API endpoints with the proper authorization expressed by an access token.
Take a look at this article for a basic authorization check for ASP.NET Core Web APIs, and this one for a permission-based approach. The articles also show how to test your protected API.
I hope these directions may help.

Using Client Credetials as the part of Usermanager<User>

Currently i am using Password Flow with my SPA application.
Now i had to integrate Client Credentials flow with my current implementation.
var descriptor = new OpenIddictApplicationDescriptor
{
ClientId = "console",
ClientSecret = "388D45FA-B36B-4988-BA59-B187D329C207",
DisplayName = "My client application",
Permissions =
{
OpenIddictConstants.Permissions.Endpoints.Token,
OpenIddictConstants.Permissions.GrantTypes.ClientCredentials
}
I have successfully implemented the both integration on my system. When sending the request using the client_id the app is authorized and authenticated as expected.
But as i notice that when using the Password flow i as using ApplicationUser as a part of Asp.net identity from dbo.User table.
But client credentials comes from different namespace that is not linked up with the Application User.
How can i setup the client_id as the part of dbo.User. How to link the the Client Credetials with Usermanager<User>?
Why i want this implementation is because during the crud operation i have the column name CreatedBy. which is refrenced to table dbo.User and i wanted to fill up the Id from dbo.User. But in the client credetials flow do not have the User.
Getting UserId
public interface IUserResolverService
{
HttpContext HttpContext { get; }
Task<Guid> GetUserId();
}
public class UserResolverService : IUserResolverService
{
IHostingEnvironment _hostingEnv;
private readonly IHttpContextAccessor accessor;
private readonly UserManager<User> _user;
public UserResolverService(IHttpContextAccessor accessor, UserManager<User> user, IHostingEnvironment hostingEnv)
{
this._user = user;
this.accessor = accessor;
this._hostingEnv = hostingEnv;
}
public HttpContext HttpContext
{
get { return accessor.HttpContext; }
}
public async Task<Guid> GetUserId()
{
var user = await _user.GetUserAsync(accessor.HttpContext.User);
return user?.Id ?? Guid.Empty;
}
}
Can you suggest me a way for this scenario?
How can i setup the client_id as the part of dbo.User. How to link the the Client Credetials with Usermanager?
It's not possible and it doesn't make any sense, actually. The client credentials is the only OAuth 2.0 core flow that doesn't involve any user as it's been designed for service-to-service scenarios, where the client application doesn't act on behalf of a user, but directly under its own identity.
To work around your issue, you may want to either make CreatedBy nullable or use one of the claim representing the application you use in AuthorizationController, like the client identifier or the client name.

ServiceStack and .NET Core Middleware

I wonder if it is possible to use ServiceStack on .NET core along with middleware.
My use case is that I'd like to have API implemented with ServiceStack, and use authorisation policies and openid connect middleware such as IdentityServer4.AccessTokenValidation.
If no, is there any alternative way to setup ServiceStack to work with openid connect server?
I glued ServiceStack and .NET Core with the following code. I am not sure if it is the best way to archive the goal, but so far it works.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class AuthorizeAttribute : RequestFilterAsyncAttribute, IAuthorizeData
{
public AuthorizeAttribute(ApplyTo applyTo)
: base(applyTo)
{
Priority = -100;
}
public AuthorizeAttribute()
: this(ApplyTo.All)
{
}
public string Policy { get; set; }
public string Roles { get; set; }
public string AuthenticationSchemes { get; set; }
public override async Task ExecuteAsync(IRequest req, IResponse res, object requestDto)
{
var policyProvider = req.TryResolve<IAuthorizationPolicyProvider>();
var filter = new AuthorizeFilter(policyProvider, new[] {this});
var webRequest = req.OriginalRequest as HttpRequest;
var actionContext = new ActionContext(webRequest.HttpContext, new RouteData(), new ActionDescriptor());
var context = new AuthorizationFilterContext(actionContext, new List<IFilterMetadata>());
await filter.OnAuthorizationAsync(context);
if (context.Result is ChallengeResult)
{
res.StatusCode = 401;
res.EndRequest();
}
else if (context.Result is ForbidResult)
{
res.StatusCode = 403;
res.EndRequest();
}
}
}
You can register .NET Core Middleware in the same AppHost as ServiceStack in .NET Core Apps since ServiceStack is just another middleware in the pipeline itself but ServiceStack built-in Authentication attributes and features wont be aware about any external Authentication implementations.
For a more integrated solution check out the community contributed ServiceStack.Authentication.IdentityServer which includes a ServiceStack AuthProvider for IdentityServer.

Basic Authentication in ASP.NET Core

Question
How can I implement Basic Authentication with Custom Membership in an ASP.NET Core web application?
Notes
In MVC 5 I was using the instructions in this article which requires adding a module in the WebConfig.
I am still deploying my new MVC Coreapplication on IIS but this approach seems not working.
I also do not want to use the IIS built in support for Basic authentication, since it uses Windows credentials.
ASP.NET Security will not include Basic Authentication middleware due to its potential insecurity and performance problems.
If you require Basic Authentication middleware for testing purposes, then please look at https://github.com/blowdart/idunno.Authentication
ASP.NET Core 2.0 introduced breaking changes to Authentication and Identity.
On 1.x auth providers were configured via Middleware (as the accepted answer's implementation).
On 2.0 it's based on services.
Details on MS doc:
https://learn.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/identity-2x
I've written a Basic Authentication implementation for ASP.NET Core 2.0 and publish to NuGet:
https://github.com/bruno-garcia/Bazinga.AspNetCore.Authentication.Basic
I'm disappointed by the ASP.NET Core authentication middleware design. As a framework it should simplify and led to greater productivity which isn't the case here.
Anyway, a simple yet secure approach is based on the Authorization filters e.g. IAsyncAuthorizationFilter. Note that an authorization filter will be executed after the other middlewares, when MVC picks a certain controller action and moves to filter processing. But within filters, authorization filters are executed first (details).
I was just going to comment on Clays comment to Hector's answer but didn't like Hectors example throwing exceptions and not having any challenge mechanism, so here is a working example.
Keep in mind:
Basic authentication without HTTPS in production is extremely bad. Make sure your HTTPS settings are hardened (e.g. disable all SSL and TLS < 1.2 etc.)
Today, most usage of basic authentication is when exposing an API that's protected by an API key (see Stripe.NET, Mailchimp etc). Makes for curl friendly APIs that are as secure as the HTTPS settings on the server.
With that in mind, don't buy into any of the FUD around basic authentication. Skipping something as basic as basic authentication is high on opinion and low on substance. You can see the frustration around this design in the comments here.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace BasicAuthFilterDemo
{
public class BasicAuthenticationFilterAttribute : Attribute, IAsyncAuthorizationFilter
{
public string Realm { get; set; }
public const string AuthTypeName = "Basic ";
private const string _authHeaderName = "Authorization";
public BasicAuthenticationFilterAttribute(string realm = null)
{
Realm = realm;
}
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
try
{
var request = context?.HttpContext?.Request;
var authHeader = request.Headers.Keys.Contains(_authHeaderName) ? request.Headers[_authHeaderName].First() : null;
string encodedAuth = (authHeader != null && authHeader.StartsWith(AuthTypeName)) ? authHeader.Substring(AuthTypeName.Length).Trim() : null;
if (string.IsNullOrEmpty(encodedAuth))
{
context.Result = new BasicAuthChallengeResult(Realm);
return;
}
var (username, password) = DecodeUserIdAndPassword(encodedAuth);
// Authenticate credentials against database
var db = (ApplicationDbContext)context.HttpContext.RequestServices.GetService(typeof(ApplicationDbContext));
var userManager = (UserManager<User>)context.HttpContext.RequestServices.GetService(typeof(UserManager<User>));
var founduser = await db.Users.Where(u => u.Email == username).FirstOrDefaultAsync();
if (!await userManager.CheckPasswordAsync(founduser, password))
{
// writing to the Result property aborts rest of the pipeline
// see https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-3.0#cancellation-and-short-circuiting
context.Result = new StatusCodeOnlyResult(StatusCodes.Status401Unauthorized);
}
// Populate user: adjust claims as needed
var claims = new[] { new Claim(ClaimTypes.Name, username, ClaimValueTypes.String, AuthTypeName) };
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, AuthTypeName));
context.HttpContext.User = principal;
}
catch
{
// log and reject
context.Result = new StatusCodeOnlyResult(StatusCodes.Status401Unauthorized);
}
}
private static (string userid, string password) DecodeUserIdAndPassword(string encodedAuth)
{
var userpass = Encoding.UTF8.GetString(Convert.FromBase64String(encodedAuth));
var separator = userpass.IndexOf(':');
if (separator == -1)
return (null, null);
return (userpass.Substring(0, separator), userpass.Substring(separator + 1));
}
}
}
And these are the supporting classes
public class StatusCodeOnlyResult : ActionResult
{
protected int StatusCode;
public StatusCodeOnlyResult(int statusCode)
{
StatusCode = statusCode;
}
public override Task ExecuteResultAsync(ActionContext context)
{
context.HttpContext.Response.StatusCode = StatusCode;
return base.ExecuteResultAsync(context);
}
}
public class BasicAuthChallengeResult : StatusCodeOnlyResult
{
private string _realm;
public BasicAuthChallengeResult(string realm = "") : base(StatusCodes.Status401Unauthorized)
{
_realm = realm;
}
public override Task ExecuteResultAsync(ActionContext context)
{
context.HttpContext.Response.StatusCode = StatusCode;
context.HttpContext.Response.Headers.Add("WWW-Authenticate", $"{BasicAuthenticationFilterAttribute.AuthTypeName} Realm=\"{_realm}\"");
return base.ExecuteResultAsync(context);
}
}
We implemented Digest security for an internal service by using an ActionFilter:
public class DigestAuthenticationFilterAttribute : ActionFilterAttribute
{
private const string AUTH_HEADER_NAME = "Authorization";
private const string AUTH_METHOD_NAME = "Digest ";
private AuthenticationSettings _settings;
public DigestAuthenticationFilterAttribute(IOptions<AuthenticationSettings> settings)
{
_settings = settings.Value;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
ValidateSecureChannel(context?.HttpContext?.Request);
ValidateAuthenticationHeaders(context?.HttpContext?.Request);
base.OnActionExecuting(context);
}
private void ValidateSecureChannel(HttpRequest request)
{
if (_settings.RequireSSL && !request.IsHttps)
{
throw new AuthenticationException("This service must be called using HTTPS");
}
}
private void ValidateAuthenticationHeaders(HttpRequest request)
{
string authHeader = GetRequestAuthorizationHeaderValue(request);
string digest = (authHeader != null && authHeader.StartsWith(AUTH_METHOD_NAME)) ? authHeader.Substring(AUTH_METHOD_NAME.Length) : null;
if (string.IsNullOrEmpty(digest))
{
throw new AuthenticationException("You must send your credentials using Authorization header");
}
if (digest != CalculateSHA1($"{_settings.UserName}:{_settings.Password}"))
{
throw new AuthenticationException("Invalid credentials");
}
}
private string GetRequestAuthorizationHeaderValue(HttpRequest request)
{
return request.Headers.Keys.Contains(AUTH_HEADER_NAME) ? request.Headers[AUTH_HEADER_NAME].First() : null;
}
public static string CalculateSHA1(string text)
{
var sha1 = System.Security.Cryptography.SHA1.Create();
var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(text));
return Convert.ToBase64String(hash);
}
}
Afterwards you can annotate the controllers or methods you want to be accessed with Digest security:
[Route("api/xxxx")]
[ServiceFilter(typeof(DigestAuthenticationFilterAttribute))]
public class MyController : Controller
{
[HttpGet]
public string Get()
{
return "HELLO";
}
}
To implement Basic security, simply change the DigestAuthenticationFilterAttribute to not use SHA1 but direct Base64 decoding of the Authorization header.
Super-Simple Basic Authentication in .NET Core:
1. Add this utility method:
static System.Text.Encoding ISO_8859_1_ENCODING = System.Text.Encoding.GetEncoding("ISO-8859-1");
public static (string, string) GetUsernameAndPasswordFromAuthorizeHeader(string authorizeHeader)
{
if (authorizeHeader == null || !authorizeHeader.Contains("Basic "))
return (null, null);
string encodedUsernamePassword = authorizeHeader.Substring("Basic ".Length).Trim();
string usernamePassword = ISO_8859_1_ENCODING.GetString(Convert.FromBase64String(encodedUsernamePassword));
string username = usernamePassword.Split(':')[0];
string password = usernamePassword.Split(':')[1];
return (username, password);
}
2. Update controller action to get username and password from Authorization header:
public async Task<IActionResult> Index([FromHeader]string Authorization)
{
(string username, string password) = GetUsernameAndPasswordFromAuthorizeHeader(Authorization);
// Now use username and password with whatever authentication process you want
return View();
}
Example
This example demonstrates using this with ASP.NET Core Identity.
public class HomeController : Controller
{
private readonly UserManager<IdentityUser> _userManager;
public HomeController(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
[AllowAnonymous]
public async Task<IActionResult> MyApiEndpoint([FromHeader]string Authorization)
{
(string username, string password) = GetUsernameAndPasswordFromAuthorizeHeader(Authorization);
IdentityUser user = await _userManager.FindByNameAsync(username);
bool successfulAuthentication = await _userManager.CheckPasswordAsync(user, password);
if (successfulAuthentication)
return Ok();
else
return Unauthorized();
}
}

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).