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.