Updated:
Pinpoint helped me get this prototype off the launch pad - I was very close except for:
- I need to upgrade to the beta6 SDK as per these instructions. Global.json now appears as follows:
{
"projects": [ "src", "test" ],
"sdk": {
"version": "1.0.0-beta6"
}
}
- I updated references in project.json:
{
"webroot": "wwwroot",
"version": "1.0.0-*",
"dependencies": {
"Microsoft.AspNet.Mvc": "6.0.0-beta6",
"Microsoft.AspNet.Server.IIS": "1.0.0-beta6",
"Microsoft.AspNet.Server.WebListener": "1.0.0-beta6",
"Microsoft.AspNet.StaticFiles": "1.0.0-beta6",
"System.IdentityModel.Tokens": "5.0.0-beta6-207211625",
"Serilog.Framework.Logging": "1.0.0-beta-43",
"Microsoft.AspNet.Authentication.OAuthBearer": "1.0.0-beta6"
},
"commands": {
"web": "Microsoft.AspNet.Hosting --config hosting.ini"
},
"frameworks": {
"dnx451": { }
},
"exclude": [
"wwwroot",
"node_modules",
"bower_components"
],
"publishExclude": [
"node_modules",
"bower_components",
"**.xproj",
"**.user",
"**.vspscc"
]
}
- The middleware order in startup's Configure method matters. UseOAuthBearerAuthentication needs to come before UseMvc. The Configure method in Startup.cs now appears as follows:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseOAuthBearerAuthentication();
app.UseMvc();
}
I'm working with ASP.NET 5 and trying to implement an extremely simple proof of concept for generating and consuming JWT tokens. I have read the articles here, here and here but this one most closely matches my needs.
To this end, I very carefully read the article, re-read it, internalized all the comments and then stood up a simple example. I can now generate a JWT token, but when I attempt to call my controller action which has been decorated with an authorize attribute [Authorize("Bearer")], I receive the following message:
The following authentication scheme was not accepted: Bearer
As I have not seen a high-fidelity A-to-Z example on how to do this, consider the following steps to reproduce:
- Create a new Web API Project in Visual Studio 2015 (I am using Enterprise) by selecting "New Project...Web...ASP.NET Web Application" and then the "Web API" option under the "ASP.NET 5 Preview Templates"
- Using the beta 5 SDK, global.json looks like this:
{
"projects": [ "src", "test" ],
"sdk": {
"version": "1.0.0-beta5",
"runtime": "clr",
"architecture": "x86"
}
}
- Bringing in the dependencies necessary for JWT tokens, project.json looks like this:
{
"webroot": "wwwroot",
"version": "1.0.0-*",
"dependencies": {
"Microsoft.AspNet.Mvc": "6.0.0-beta6",
"Microsoft.AspNet.Server.IIS": "1.0.0-beta6",
"Microsoft.AspNet.Server.WebListener": "1.0.0-beta6",
"System.IdentityModel.Tokens": "5.0.0-beta5-206011020",
"Microsoft.AspNet.Authentication.OAuthBearer": "1.0.0-beta5"
},
"commands": {
"web": "Microsoft.AspNet.Hosting --config hosting.ini"
},
"frameworks": {
"dnx451": { }
},
"exclude": [
"wwwroot",
"node_modules",
"bower_components"
],
"publishExclude": [
"node_modules",
"bower_components",
"**.xproj",
"**.user",
"**.vspscc"
]
}
- Startup.cs (this is an example not intended for production)
public class Startup
{
const string _TokenIssuer = "contoso.com" ;
const string _TokenAudience = "contoso.com/resources" ;
RsaSecurityKey _key = null ;
SigningCredentials _signingCredentials = null ;
public Startup(IHostingEnvironment env)
{
GenerateRsaKeys();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddInstance(_signingCredentials);
services.ConfigureOAuthBearerAuthentication
(
options =>
{
options.AutomaticAuthentication = true;
options.TokenValidationParameters.IssuerSigningKey = _key ;
options.TokenValidationParameters.ValidAudience = _TokenAudience;
options.TokenValidationParameters.ValidIssuer = _TokenIssuer ;
}
);
services.ConfigureAuthorization
(
options =>
{
options.
AddPolicy
(
"Bearer",
new AuthorizationPolicyBuilder().
AddAuthenticationSchemes(OAuthBearerAuthenticationDefaults.AuthenticationScheme).
RequireAuthenticatedUser().
Build()
);
}
);
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env,ILoggerFactory loggerfactory)
{
app.UseMvc();
app.UseOAuthBearerAuthentication();
}
void GenerateRsaKeys()
{
using(RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048))
{
_key = new RsaSecurityKey(rsa.ExportParameters(true));
_signingCredentials =
new SigningCredentials
(
_key ,
SecurityAlgorithms.RsaSha256Signature ,
SecurityAlgorithms.Sha256Digest ,
"secret"
);
rsa.PersistKeyInCsp = false;
}
}
}
Credentials.cs
public class Credentials
{
public string user { set;get;}
public string password { set;get;}
}
JwtToken.cs
public class JwtToken
{
public string access_token { set; get; }
public string token_type { set; get; }
}
- A token controller for fetching a token (this is an example not intended for production), TokenController.cs:
[ Route("[controller]") ]
public class TokenController : Controller
{
private readonly OAuthBearerAuthenticationOptions _bearerOptions ;
private readonly SigningCredentials _signingCredentials ;
public TokenController
(
IOptions<OAuthBearerAuthenticationOptions> bearerOptions ,
SigningCredentials signingCredentials
)
{
_bearerOptions = bearerOptions.Options ;
_signingCredentials = signingCredentials ;
}
// POST: /token
[HttpPost()]
public JwtToken Token([FromBody] Credentials credentials)
{
// Pretend to validate credentials...
JwtSecurityTokenHandler handler =
_bearerOptions .
SecurityTokenValidators .
OfType<JwtSecurityTokenHandler>() .
First();
JwtSecurityToken securityToken =
handler .
CreateToken
(
issuer : _bearerOptions.TokenValidationParameters.ValidIssuer ,
audience : _bearerOptions.TokenValidationParameters.ValidAudience,
signingCredentials : _signingCredentials ,
subject : new ClaimsIdentity
(
new Claim []
{
new Claim(ClaimTypes.Name,"somebody"),
new Claim(ClaimTypes.Role,"admin" ),
new Claim(ClaimTypes.Role,"teacher" ),
}
) ,
expires : DateTime.Today.AddDays(1)
);
string token = handler.WriteToken(securityToken);
return new JwtToken()
{
access_token = token ,
token_type = "bearer"
};
}
}
- A values controller to demonstrate ingesting the token, ValuesController.cs:
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET: api/values
[Authorize("Bearer")]
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
}
- Fire up a copy of postman (or your favorite REST client), launch the sample app under Visual Studio and make a POST request, similar to http: // localhost:22553/token/ with the JSON body:
{
"user" : "user",
"password" : "secret"
}
The app responds with a token:
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6bnVsbH0.eyJ1bmlxdWVfbmFtZSI6InNvbWVib2R5Iiwicm9sZSI6WyJhZG1pbiIsInRlYWNoZXIiXSwiaXNzIjoiY29udG9zby5jb20iLCJhdWQiOiJjb250b3NvLmNvbS9yZXNvdXJjZXMiLCJleHAiOjE0Mzk1MzU2MDB9.anRgL10XFG_bKDDxY3D2xQSfhPRLGMjUTreQNsP1jDA6eRKwXHf3jtpCwm_saoWyUDFFA2TMI9e_LbP6F5l7vtozCluziE_GQkPkspUSWuWIpQJLPRTTPPZHGKmPmK4MLEl1zPPrggJWbvF9RBw3mMQ0KoMfjSL0vUQ8kZ7VXAel8dnYJccd-CFdnB6aDe79x2E9Se2iLxdhr--R_qgvfz1Fa6tR1dstqLQ-UjYqPWY4SOgBjM3abtjfLLVEzeQMVyezX7Cx9ObMXAGbGvQL6GB_T5RlfAoXWME4jM8Bzhd-07wwd732bBws4OXivj1sSz-qawNTnXmnuccLRtI1uA",
"token_type": "bearer"
}
Copy the token from the previous POST, then in postman make a GET request similar to http: // localhost:22553/api/values, taking care to add an Authorization header with the value "bearer YOURTOKEN" (e.g. bearer eyJ0eXAiOiJKV1QiLCJ...)
Observe that the app responds with the error:
System.InvalidOperationException
The following authentication scheme was not accepted: Bearer
Stack trace as follows:
<pre class="lang-none prettyprint-overrid