Authenticating an MVC application with tokens WebAPI - model-view-controller

I have written a basic WebAPI that authenticates using Jwt Tokens. When I make a test API call using Postman I get a token issued successfully. I am however stuck in getting my MVC application to authenticate using the tokens.
Here is the controller for the API -
[HttpPost]
[Route("login")]
public async Task<IActionResult> Login([FromBody] LoginModel model)
{
var user = await userManager.FindByNameAsync(model.Username);
if (user != null && await userManager.CheckPasswordAsync(user, model.Password))
{
var authClaims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("YVBy0OLlMQG6VVVp1OH7Xzyr7gHuw1qvUC5dcGt3SBM="));
var token = new JwtSecurityToken(
issuer: "https://localhost:44350",
audience: "https://localhost:44350",
expires: DateTime.Now.AddHours(3),
claims: authClaims,
signingCredentials: new Microsoft.IdentityModel.Tokens.SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256)
);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo
});
}
return Unauthorized();
}
Here is the Startup.cs for the WebAPI -
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = "https://localhost:44350/",
ValidIssuer = "https://localhost:44350/",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("AA873344gshtrhjLJKJSLKF8u4o8grwieot4KJHFs9847GGSD"))
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
SeedDB.Initialize(app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope().ServiceProvider);
}
}
My code for the MVC applicaiton controller is below. It generates a token successfully, but I cant work out how to populate HttpContext.User.Identity?
[HttpPost]
public async Task<ActionResult> Index(LoginModel login)
{
string url = BaseUrl + "api/authenticate/login";
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(url);
var postTask = client.PostAsJsonAsync<LoginModel>("login", login);
postTask.Wait();
var result = postTask.Result;
if (result.IsSuccessStatusCode)
{
var user = HttpContext.User.Identity as ClaimsIdentity;
var tokenDetails = JsonConvert.DeserializeObject<Dictionary<string, string>>(result.Content.ReadAsStringAsync().Result);
var claims = new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, login.Username, "string"),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTime.Now.Ticks.ToString(), ClaimValueTypes.Integer64)
};
user.AddClaims(claims);
return RedirectToAction("Index", "Home", null);
}
}
ModelState.AddModelError(string.Empty, "Server error");
return View(login);
}
Here is the Startup.cs for the MVC application
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}

You should add authentication service and use authentication middleware in the startup.cs:
services.AddAuthentication(option =>
{
option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(option =>
{
option.RequireHttpsMetadata = false;
option.SaveToken = true;
option.TokenValidationParameters = new TokenValidationParameters
{
RequireExpirationTime = true,
ValidateLifetime = true,
ValidIssuer = "Some Issuer",
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = "Some Audience",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Security Key")),
ValidateIssuerSigningKey = true,
};
});
Also add app.UseAuthentication(); to the Configure method in startup.cs.

Related

HttpContext.Session.GetString("string"), Randomly I get Session has not been configured for this application or request exception

Not everytime but some time i get error while i try to get HttpContext.Session.GetString("userid");
I am using .Net core 2.2
Below is my session helper code
public static class SessionHelper
{
private static HttpContext _context = null;
public static void SetCurrentContext(HttpContext context)
{
_context = context;
}
public static string UserID
{
get
{
try
{
return _context.Session.GetString("userid");
}
catch
{
return "";
}
}
set
{
_context.Session.SetString("userid", value);
}
}
}
Below in my middleware configuration in Configure method startup.cs
app.UseSession();
app.Use(async (context, next) =>
{
context.Response.Headers.Add("X-Frame-Options", "sameorigin");
await next();
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
app.UseMvcWithDefaultRoute();
Below is my ConfigureServices code of startup.cs
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromSeconds(60 * 30);
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.SameAsRequest;
//options.IOTimeout = TimeSpan.FromSeconds(60 * 30);
});
services.AddMvc()
.AddSessionStateTempDataProvider();

JWT Token from postman never hits the controller with Authorize attribute

Trying to understand how JWT works for asp.net core application. I use a ASP.NET MVC Core application template.
My StartUp.cs cotains configuration for JWT Token :
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
//options.EventsType = typeof(AuthenticateCustomEvent);
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
}
And my login controller contains code to return token on successfull validation of the user this returns a token as shown below
Once i recieve the token i make a call to a controller with [Authorize] attribute applied to it with the bearer token from PostMan the controller never gets hit ? Am i missing something ?
My below solution is bit different, but this solution will help you to deal with custom auth implementation, you can implement a different type of auth for the different type of users. You require to create a class AuthorizationRequiredAttribute under your API project, this class will inherit ActionFilterAttribute class to filter each API request. you can filter all HTTP methods (GET, POST, PUT, DELETE...etc), and can implement your own authorization logic for specific HTTP method.
ActionFilterAttribute.cs
using BillSyatemCore.Common.Constants;
using BillSyatemCore.Services.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Configuration;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net;
using System.Net.Http;
namespace BillSyatemCore.Handlers
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class AuthorizationRequiredAttribute : ActionFilterAttribute
{
private IConfiguration _config;
public AuthorizationRequiredAttribute(IConfiguration config)
{
_config = config;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
try
{
if (context.HttpContext.Request.Headers.ContainsKey(Constants.HttpHeaders.Token))
{
var handler = new JwtSecurityTokenHandler();
var token = handler.ReadToken(context.HttpContext.Request.Headers[Constants.HttpHeaders.Token])
as JwtSecurityToken;
var expireDate = Convert.ToDateTime(token.Claims.First(claim => claim.Type == Constants.JwtClaims.ExpiresOn).Value);
if (context.HttpContext.Request.Method == WebRequestMethods.Http.Get)
{
if (expireDate < DateTime.Now)
{
context.Result = new UnauthorizedResult();
}
}
else
{
//You may filter post,put,delete etc request here.
}
}
else
{
context.Result = new NotFoundResult();
}
}
catch (Exception ex)
{
context.Result = new BadRequestResult();
}
base.OnActionExecuting(context);
}
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//JWT
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.Build());
});
}
Controller.cs
using BillSyatemCore.Models.Authentication;
using BillSystemCore.Transporter;
using Microsoft.AspNetCore.Mvc;
namespace TestProject.Controllers
{
[Produces("application/json")]
[Route("api/[controller]")]
public class UserTypeController : Controller
{
private readonly IAuthTransporter _authTransporter;
public UserTypeController(IAuthTransporter authTransporter)
{
_authTransporter = authTransporter;
}
[HttpPost("save"), ServiceFilter(typeof(AuthorizationRequiredAttribute))]
public IActionResult Save([FromBody] UserType userType)
{
return Ok(_authTransporter.UserTypeServices.Save(userType));
}
}
}

Postman is giving 401 instead of 403 on Web API

With this code:
MyAuthorizationServerProvider
public class MyAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated(); //
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
if (context.UserName == "admin" && context.Password == "admin")
{
identity.AddClaim(new Claim(ClaimTypes.Role, "admin"));
identity.AddClaim(new Claim("username", "admin"));
identity.AddClaim(new Claim(ClaimTypes.Name, "Juan Dela Cruz"));
context.Validated(identity);
}
else if (context.UserName == "user" && context.Password == "user")
{
identity.AddClaim(new Claim(ClaimTypes.Role, "user"));
identity.AddClaim(new Claim("username", "user"));
identity.AddClaim(new Claim(ClaimTypes.Name, "Barok Kabundukan"));
context.Validated(identity);
}
else
{
context.SetError("invalid_grant", "Provided username or password is incorrect");
return;
}
}
}
AuthorizeAttribute
protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if (!HttpContext.Current.User.Identity.IsAuthenticated)
{
base.HandleUnauthorizedRequest(actionContext);
}
else
{
actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Forbidden);
}
}
Startup
public class Startup
{
public void Configuration(IAppBuilder app)
{
//enable cors origin requests
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
var myProvider = new MyAuthorizationServerProvider();
OAuthAuthorizationServerOptions options = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = myProvider
};
app.UseOAuthAuthorizationServer(options);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
HttpConfiguration config = new HttpConfiguration();
WebApiConfig.Register(config);
}
}
DataController
public class DataController : ApiController
{
[AllowAnonymous]
[HttpGet]
[Route("api/data/forall")]
public IHttpActionResult Get()
{
return Ok("Server time is: " + DateTime.Now);
}
[Authorize]
[HttpGet]
[Route("api/data/authenticate")]
public IHttpActionResult GetForAuthenticate()
{
var identity = (ClaimsIdentity)User.Identity;
return Ok("Hello " + identity.Name);
}
[Authorize(Roles = "admin")]
[HttpGet]
[Route("api/data/authorize")]
public IHttpActionResult GetForAdmin()
{
var identity = (ClaimsIdentity)User.Identity;
var roles = identity.Claims
.Where(c => c.Type == ClaimTypes.Role)
.Select(c => c.Value);
return Ok("Hello " + identity.Name + " Role: " + string.Join(",", roles.ToList()));
}
}
With that code, I am able to get the token and authenticate. The problem however is when I use the "user" credentials and authenticate then access the method that has Authorize(Role="admin"), in the Postman, I get "401". I should be getting the "403" since I am authenticated already but do not have a role of "admin". Can you please show me where I am getting it wrong? Thank you.
No, you should still get 401. 401 means unauthorized, not unauthenticated. In your scenario, you do not have the admin role, so you are not authorized to access the requested resource.
EDIT:
I don't usually post Wikipedia, but I think the article on 403 explains this pretty well; see the section on the differences between 401 and 403:
https://en.wikipedia.org/wiki/HTTP_403

SignalR Authorize attribute not called

I have a hub that does not convert the token located at Authorization:Bearer eyjsdalfsadlfjffdafs... in the request header to an identity. The rest of the API works fine with standard http verbs however for some reason SignalR is not authorizing the token into a user.
public class ChatHub : Hub
{
[Authorize]
public override Task OnConnected()
{
// error context.user.identity.name =""
var userId = int.Parse(Context.User.Identity.Name);
return base.OnConnected();
}
....
}
Startup.cs
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
JwtHandler - this part of the filter is not called when the client connects to the hub even though onConnect() is attributed with [Authorize]
public class JwtHandler : DelegatingHandler
{
private const string ISSUER = "Issuer";
private const string AUDIENCE = "Audience";
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
byte[] key = Convert.FromBase64String("SecretKey");
try
{
var headers = request.Headers;
if(headers.Authorization != null)
{
if(headers.Authorization.Scheme.Equals("Bearer"))
{
string jwt = request.Headers.Authorization.Parameter;
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
TokenValidationParameters parms = new TokenValidationParameters()
{
ValidAudience = AUDIENCE,
ValidIssuers = new List<string>(){ISSUER},
IssuerSigningToken = new BinarySecretSecurityToken(key),
};
SecurityToken validated = new JwtSecurityToken(jwt);
var principal = tokenHandler.ValidateToken(jwt, parms,out validated);
Thread.CurrentPrincipal = principal;
if(HttpContext.Current !=null)
{
HttpContext.Current.User = principal;
}
}
}
var response = await base.SendAsync(request, cancellationToken);
if(response.StatusCode == HttpStatusCode.Unauthorized)
{
response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Bearer", "error=\"invalid_token\""));
return response;
}
return response;
}catch (Exception)
{
var response = request.CreateResponse(HttpStatusCode.Unauthorized);
response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Bearer", "error=\"invalid_token\""));
return response;
}
}
}
Try validating the jwt token in the OWIN middleware. In your Startup.cs add:
public void Configuration(IAppBuilder app)
{
app.UseJwtBearerAuthentication(
new Microsoft.Owin.Security.Jwt.JwtBearerAuthenticationOptions() {
AllowedAudiences = new string[] { ALLOWEDAUDIENCE },
IssuerSecurityTokenProviders = new[] {
new SymmetricKeyIssuerSecurityTokenProvider(ISSUER, System.Convert.FromBase64String(cKey))
}
});
app.MapSignalR();
}

Web API 2 OWIN Bearer token authentication - AccessTokenFormat null?

I have an existing ASP.NET MVC 5 project and I'm adding a Web API 2 project to it. I want to use bearer token authentication and have followed Hongye Sun's tutorial "OWIN Bearer Token Authentication with Web API Sample" and this question as well.
In my Login method, for the line Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);, the AccessTokenFormat is null. Any idea why?
My AccountController:
[RoutePrefix("api")]
public class AccountController : ApiController
{
public AccountController() {}
// POST api/login
[HttpPost]
[Route("login")]
public HttpResponseMessage Login(int id, string pwd)
{
if (id > 0) // testing - not authenticating right now
{
var identity = new ClaimsIdentity(Startup.OAuthBearerOptions.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, id.ToString()));
AuthenticationTicket 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.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ObjectContent<object>(new
{
UserName = id.ToString(),
AccessToken = token
}, Configuration.Formatters.JsonFormatter)
};
}
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
// POST api/token
[Route("token")]
[HttpPost]
public HttpResponseMessage Token(int id, string pwd)
{
// Never reaches here. Do I need this method?
return new HttpResponseMessage(HttpStatusCode.OK);
}
}
Startup class:
public class Startup
{
private static readonly ILog _log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static Func<MyUserManager> UserManagerFactory { get; set; }
public static string PublicClientId { get; private set; }
static Startup()
{
PublicClientId = "MyWeb";
UserManagerFactory = () => new MyUserManager(new UserStore<MyIdentityUser>());
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/api/token"),
Provider = new MyWebOAuthProvider(PublicClientId, UserManagerFactory),
AuthorizeEndpointPath = new PathString("/api/login"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
}
public void Configuration(IAppBuilder app)
{
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/api/login")
});
// Configure Web API to use only bearer token authentication.
var config = GlobalConfiguration.Configuration;
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthBearerOptions.AuthenticationType));
app.UseWebApi(config);
}
}
MyIdentityUser just adds an extra property:
public class MyIdentityUser : IdentityUser
{
public int SecurityLevel { get; set; }
}
MyUserManager calls my custom user authentication method to an internal server:
public class MyUserManager : UserManager<MyIdentityUser>
{
public MyUserManager(IUserStore<MyIdentityUser> store) : base(store) { }
public MyIdentityUser ValidateUser(int id, string pwd)
{
LoginIdentityUser user = null;
if (MyApplication.ValidateUser(id, pwd))
{
// user = ??? - not yet implemented
}
return user;
}
}
MyWebOAuthProvider (I took this from the SPA template. Only GrantResourceOwnerCredentials has been changed):
public class MyWebOAuthProvider : OAuthAuthorizationServerProvider
{
private readonly string _publicClientId;
private readonly Func<MyUserManager> _userManagerFactory;
public MyWebOAuthProvider(string publicClientId, Func<MyUserManager> userManagerFactory)
{
if (publicClientId == null)
{
throw new ArgumentNullException("publicClientId");
}
if (userManagerFactory == null)
{
throw new ArgumentNullException("userManagerFactory");
}
_publicClientId = publicClientId;
_userManagerFactory = userManagerFactory;
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
using (MyUserManager userManager = _userManagerFactory())
{
MyIdentityUser user = null;
var ctx = context as MyWebOAuthGrantResourceOwnerCredentialsContext;
if (ctx != null)
{
user = userManager.ValidateUser(ctx.Id, ctx.Pwd);
}
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await userManager.CreateIdentityAsync(user,
context.Options.AuthenticationType);
ClaimsIdentity cookiesIdentity = await userManager.CreateIdentityAsync(user,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
... // unchanged from SPA template
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
... // unchanged from SPA template
}
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
... // unchanged from SPA template
}
public static AuthenticationProperties CreateProperties(string userName)
{
... // unchanged from SPA template
}
}
MyWebOAuthGrantResourceOwnerCredientialsContext:
public class MyWebOAuthGrantResourceOwnerCredentialsContext : OAuthGrantResourceOwnerCredentialsContext
{
public MyWebOAuthGrantResourceOwnerCredentialsContext (IOwinContext context, OAuthAuthorizationServerOptions options, string clientId, string userName, string password, IList<string> scope)
: base(context, options, clientId, userName, password, scope)
{ }
public int Id { get; set; }
public string Pwd { get; set; }
}
How is AccessTokenFormat set? Is what I've set up correct? I'm not authenticating against any external services, just a legacy internal server.
Thanks.
I had the same problem - it was to do with my initialisation in Startup().
Like you, I was storing the OAuthBearerOptions in a static field:
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
But then I was wrongly using a new instance of the same class later on:
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()); // wrong!
Obviously the fix was to use the static field instead:
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
In fact, it doesn't look like you call UseOAuthBearerAuthentication() at all. I followed this excellent series of posts by Taiseer Joudeh.
Full Startup.cs:
public class Startup
{
public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
public void ConfigureOAuth(IAppBuilder app)
{
//use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ExternalCookie);
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions() {
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider() // see post
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
//[Configure External Logins...]
}
}
I'm not sure if you're still looking for the answer to this - but here's a bit of code that I'm using in my AngularJS app to get the security token from my WebAPI2 endpoint.
$http({
method: 'POST', url: '/token', data: { username: uName, password: uPassword, grant_type: 'password' },
transformRequest: function (obj) {
var str = [];
for (var p in obj)
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
return str.join("&");
}
}).success(function (data, status, headers, config) {
console.log("http success", data);
accessToken.value = data.access_token;
console.log("access token = ", accessToken.value);
}).error(function (data, status, headers, config) {
console.log("http error", data);
});
I can then pass the accessToken in the header of any other requests in order to get the authentication validation.
I have removed the sample code as it can cause confusion when it's used with Web API and SPA template. You'd better stay with the template code to use OAuth authorization server to generate token. In your scenario, you should use resource owner password grant to authenticate the user. Please check my blog on SPA template which has details about the password flow on http://blogs.msdn.com/b/webdev/archive/2013/09/20/understanding-security-features-in-spa-template.aspx
Instead of writing your own Web API to handle login, you need to use OWIN OAuth Server's /token endpoint to handle password login.

Resources