I'm using Microsoft.Azure.Mobile SDK for implementing Server code.
Code in OWIN startup is as below:
public void ConfigureAuth(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
new MobileAppConfiguration().ApplyTo(config);
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = ConfigurationManager.AppSettings["SigningKey"],
ValidAudiences = new[] { ConfigurationManager.AppSettings["ValidAudience"] },
ValidIssuers = new[] { ConfigurationManager.AppSettings["ValidIssuer"] },
TokenHandler = config.GetAppServiceTokenHandler()
});
app.UseWebApi(config);
}
Token generation code:
Claim[] claims = new Claim[]
{
new Claim("sub", "SampleSubject"),
new Claim("Id", Convert.ToString(Users[0].user_id)),
new Claim("name", Users[0].name),
new Claim("surname", Users[0].surname),
new Claim(ClaimTypes.Role, "user")
};
var token = AppServiceLoginHandler.CreateToken(claims, ConfigurationManager.AppSettings["SigningKey"], ConfigurationManager.AppSettings["ValidAudience"], ConfigurationManager.AppSettings["ValidIssuer"], TimeSpan.FromDays(30));
return token.RawData;
A sample JWT token is
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJHcnViZXJBUEkiLCJJZCI6IjMyODkwIiwibmFtZSI6IkRhdmlkZSIsInN1cm5hbWUiOiJCb25ldHRhIiwicm9sZSI6InVzZXIiLCJ2ZXIiOiIzIiwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6MjM1MzEvIiwiYXVkIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6MjM1MzIvIiwiZXhwIjoxNTAyNDYyNDQzLCJuYmYiOjE0OTk4NzA0NDN9.b5VhWzvkaEumutPZpLzImcAy4NotXCSgUIqLltVUQWI
The token is valid per below screenshot
And for below code,
[Authorize]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
public string Get(int id)
{
try
{
ClaimsPrincipal claims;
AppServiceTokenHandler s = new AppServiceTokenHandler(new HttpConfiguration());
s.TryValidateLoginToken(Request.Headers.Authorization.Parameter, ConfigurationManager.AppSettings["SigningKey"], new[] { ConfigurationManager.AppSettings["ValidAudience"] }, new[] { ConfigurationManager.AppSettings["ValidIssuer"] }, out claims);
AppServiceTokenHandler.ValidateToken(Request.Headers.Authorization.Parameter, ConfigurationManager.AppSettings["SigningKey"], ConfigurationManager.AppSettings["ValidAudience"], ConfigurationManager.AppSettings["ValidIssuer"]);
}
catch (Exception ex)
{
throw;
}
return "value";
}
'/Get' request fails with HTTP 401.
BUT For the same JWT token 'Get/5' returns HTTP 200 (validates token manually).
The problem is, when I use Authorize attribute, api returns 401.
According to your description, I checked this issue on my side.
Access the protected Web API
In summary, you could refer to the above screen shoot and check with your api endpoint. Additionally, you could refer to AppServiceTokenHandler.cs and HmacSigningCredentials.cs for implementing your custom TokenHandler to troubleshoot this issue. Moreover, you could refer to adrian hall's book about Custom Authentication
.
Related
I am working on an Asp.net Web API + MVC project. While creating the project, I selected 'Individual User Accounts' option for authentication. After login, the provider issues access token and I use this token in HTTP header of ajax calls and it works fine with Ajax calls. I also have MVC actions which are used to navigate to different pages. These actions are protected using [Authorize] attribute of System.web.MVC. While navigating to these actions authorization fails and I am redirected to login page. How do I configure Oauth 2.0 access token to be used in non ajax requests (MVC requests) also.
Startup.Auth.cs:
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static string PublicClientId { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.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
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Home/Login"),
LogoutPath = new PathString("/Account/LogOff"),
ExpireTimeSpan = TimeSpan.FromHours(1.0),
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true // dont use this for production,
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
WebApiConfig.cs:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
ApplicationOAuthProvider.cs:
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
private readonly string _publicClientId;
public ApplicationOAuthProvider(string publicClientId)
{
if (publicClientId == null)
{
throw new ArgumentNullException("publicClientId");
}
_publicClientId = publicClientId;
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
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)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// Resource owner password credentials does not provide a client ID.
if (context.ClientId == null)
{
context.Validated();
}
return Task.FromResult<object>(null);
}
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (context.ClientId == _publicClientId)
{
Uri expectedRootUri = new Uri(context.Request.Uri, "/");
if (expectedRootUri.AbsoluteUri == context.RedirectUri)
{
context.Validated();
}
}
return Task.FromResult<object>(null);
}
public static AuthenticationProperties CreateProperties(string userName)
{
IDictionary<string, string> data = new Dictionary<string, string>
{
{ "userName", userName }
};
return new AuthenticationProperties(data);
}
}
ASP.Net MVC is a server side web app technology and [Authorize] actions expect to receive a cookie rather than checking for a token.
Not sure if this is overridable and in this type of solution you often end up having to deal with an ugly mix of cookies / tokens / server side code / client side code.
A more modern / cleaner separation is to develop a single page app + REST API instead. Code tends to be simpler and it feels like a better fit for the requirements you mention.
To see what I mean, maybe have a quick browse of the UI code in this sample:
https://github.com/gary-archer/oauth.websample1
The repo links to some blog posts if it's an option you're interested in.
Overview of my project structure:
I have 2 Projects.
Asp.net core Web Api
Asp.net core Web MVC
In Web Api Project
I am NOT using Asp.net core Identity for login, instead, I am using my own login mechanism.
LoginAction method will authenticate user in database and generate JWT Token.
I was able to generate JWT Token and Life is smooth till this point.
Generate Token
[AllowAnonymous]
[Route("requesttoken")]
[HttpPost]
public async Task<IActionResult> RequestToken([FromBody] TokenRequest request)
{
var result = await IsValidUser(request);
if(result)
{
var claims = new[]
{
new Claim(ClaimTypes.Name, request.Email)
};
var key = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_myAppSettings.SecurityKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _myAppSettings.WebsiteName.ToLower(),
audience: _myAppSettings.WebsiteName.ToLower(),
claims: claims,
notBefore: Utilities.GetEST_DateTimeNow(),
expires: Utilities.GetEST_DateTimeNow().AddMinutes(5),
signingCredentials: creds);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token)
});
}
else
{
return Unauthorized();
}
}
Inside Startup class
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyAppSettings>(Configuration.GetSection("MyAppSettings"));
#region Validate JWT Token
ConfigureJwtAuthService(services, Configuration);
#endregion
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseMvc();
}
JWT Validation part (As partial startup class)
public void ConfigureJwtAuthService(IServiceCollection services, IConfiguration configuration)
{
var symmetricKeyAsBase64 = configuration["MyAppSettings:SecurityKey"];
var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
var signingKey = new SymmetricSecurityKey(keyByteArray);
var tokenValidationParameters = new TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
// Validate the JWT Issuer (iss) claim
ValidateIssuer = true,
ValidIssuer = Configuration["MyAppSettings:WebsiteName"].ToLower(),
// Validate the JWT Audience (aud) claim
ValidateAudience = true,
ValidAudience = Configuration["MyAppSettings:WebsiteName"].ToLower(),
// Validate the token expiry
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
services.AddAuthentication(
options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o => o.TokenValidationParameters = tokenValidationParameters);
}
Sample response of LoginAction Method.
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkrDtGhuIETDs8OoIiwiYWRtaW4iOnRydWV9.469tBeJmYLERjlKi9u6gylb-2NsjHLC_6kZNdtoOGsA"
}
In Web MVC Project
I am consuming above Web Api and passing login parameters and was able to get JWT Token response.
I am storing JWT Token response in cookie [Manually - _httpContextAccessor.HttpContext.Response.Cookies.Append(key, jwtTokenValue, option);]
Based on JWT Token response receive, I am trying to extract claims from that JWT Token, so that I can able to create valid identity of user and login user on the web.
I am trying to achieve something like below:
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, model.Email)
};
var userIdentity = new ClaimsIdentity(claims, "login");
ClaimsPrincipal principal = new ClaimsPrincipal(userIdentity);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
return RedirectToLocal(returnUrl);
}
Questions
Am I doing right by storing JWT Token in cookie. Is my manual approach of storing cookie is correct or is there any better way?
How can I get claims from JWT in Web Project, so that I can able to singin user using cookie?
Want to do it right way, any help would be much appreciated.
Following seems to helped me: http://blogs.quovantis.com/json-web-token-jwt-with-web-api/ not sure whether that is right way of doing or not.
/// Using the same key used for signing token, user payload is generated back
public JwtSecurityToken GenerateUserClaimFromJWT(string authToken)
{
var tokenValidationParameters = new TokenValidationParameters()
{
ValidAudiences = new string[]
{
"http://www.example.com",
},
ValidIssuers = new string[]
{
"self",
},
IssuerSigningKey = signingKey
};
var tokenHandler = new JwtSecurityTokenHandler();
SecurityToken validatedToken;
try {
tokenHandler.ValidateToken(authToken,tokenValidationParameters, out validatedToken);
}
catch (Exception)
{
return null;
}
return validatedToken as JwtSecurityToken;
}
I wanted to make JWT auth in my MVC app. I make Authorization web service in Web API which returns token correctly. After that Im trying to store token in cookie.
[HttpPost]
public async Task<ActionResult> Login(LoginDto loginDto)
{
var token = await loginService.GetToken(loginDto);
if (!string.IsNullOrEmpty(token))
{
var cookie = new System.Web.HttpCookie("token", token)
{
HttpOnly = true
};
Response.Cookies.Add(cookie);
return RedirectToAction("Index", "Product");
}
return View("LoginFailed");
}
But now I wanted to add this token to headers for every request. So I decided action filters would be best for achieve this.
public class CustomActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var token = filterContext.HttpContext.Request.Cookies.Get("token");
if (token != null)
filterContext.HttpContext.Request.Headers.Add("Authorization", $"Bearer {token}");
base.OnActionExecuting(filterContext);
}
}
Startup
public class Startup
{
public void Configuration(IAppBuilder app)
{
AutofacConfig.Configure();
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
ConfigureOAuth(app);
}
public void ConfigureOAuth(IAppBuilder app)
{
var issuer = System.Configuration.ConfigurationManager.AppSettings["issuer"];
var audience = System.Configuration.ConfigurationManager.AppSettings["appId"];
var secret = TextEncodings.Base64Url.Decode(System.Configuration.ConfigurationManager.AppSettings["secret"]);
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audience },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
},
});
}
}
And then I just marked controller which authorize attribute. It works fine when I called it with POSTMAN.
But action filters in MVC are fired always after authorization filter. So I have questions:
How to add token from cookie to every request ? Is it good practice? If not what I should do?
How about csrf attacks and others ? Is AntiForgeryTokenAttr will do the work? What about ajax calls then?
Additional Information
This is how login service looks like. Its just making call to auth endpoint.
public class LoginService : ILoginService
{
public async Task<string> GetToken(LoginDto loginDto)
{
var tokenIssuer = ConfigurationManager.AppSettings["issuer"];
using (var httpClient = new HttpClient {BaseAddress = new Uri($"{tokenIssuer}/oauth2/token")})
{
using (var response = await httpClient.PostAsync(httpClient.BaseAddress, new FormUrlEncodedContent(
new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("username", loginDto.Username),
new KeyValuePair<string, string>("password", loginDto.Password),
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("client_id", ConfigurationManager.AppSettings["appId"])
})))
{
var contents = await response.Content.ReadAsStringAsync();
if (response.StatusCode == HttpStatusCode.OK)
{
var deserializedResponse =
new JavaScriptSerializer().Deserialize<Dictionary<string, string>>(contents);
var token = deserializedResponse["access_token"];
return token;
}
}
return null;
}
}
}
I found a solution. I just make custom OAuthBearerAuthenticationProvider provider and inside this class Im retrieve token from cookie and then assign this to context.Token
public class MvcJwtAuthProvider : OAuthBearerAuthenticationProvider
{
public override Task RequestToken(OAuthRequestTokenContext context)
{
var token = context.Request.Cookies.SingleOrDefault(x => x.Key == "token").Value;
context.Token = token;
return base.RequestToken(context);
}
}
And then inside startup.cs
public class Startup
{
public void Configuration(IAppBuilder app)
{
AutofacConfig.Configure();
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
ConfigureOAuth(app);
}
public void ConfigureOAuth(IAppBuilder app)
{
var issuer = System.Configuration.ConfigurationManager.AppSettings["issuer"];
var audience = System.Configuration.ConfigurationManager.AppSettings["appId"];
var secret = TextEncodings.Base64Url.Decode(System.Configuration.ConfigurationManager.AppSettings["secret"]);
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audience },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
},
Provider = new MvcJwtAuthProvider() // override custom auth
});
}
}
I am using Web API secured with OAuth Bearer token. When getting the token I want to send extra information to the user, so I tried the following as per this thread:
CustomOAuthProvider.cs:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// Other stuff, cut off for brevity
var user = await userManager.FindAsync(context.UserName, context.Password);
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager, "JWT");
oAuthIdentity.AddClaims(ExtendedClaimsProvider.GetClaims(user));
oAuthIdentity.AddClaims(RolesFromClaims.CreateRolesBasedOnClaims(oAuthIdentity));
var ticket = new AuthenticationTicket(oAuthIdentity, this.CreateProperties(user.UserName, oAuthIdentity));
context.Validated(ticket);
}
private AuthenticationProperties CreateProperties(string userName, ClaimsIdentity oAuthIdentity)
{
var data = new Dictionary<string, string>
{
{ "username", userName },
{ "roles", JsonConvert.SerializeObject(oAuthIdentity.Claims.Where(c=> c.Type == ClaimTypes.Role).Select(c => c.Value).ToArray()) }
};
return new AuthenticationProperties(data);
}
But the returned object is always as following:
{
access_token: "theTokenHash"
expires_in: 86399
token_type: "bearer"
}
This is my Startup.cs:
public void Configuration(IAppBuilder app)
{
// AutoMapper
AutoMapperConfig.RegisterMappings();
var httpConfig = new HttpConfiguration();
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
ConfigureOAuthTokenGeneration(app);
ConfigureOAuthTokenConsumption(app);
ConfigureWebApi(httpConfig);
WebApiConfig.Register(httpConfig);
AutofacConfig.Register(httpConfig);
app.UseWebApi(httpConfig);
httpConfig.EnsureInitialized();
}
private void ConfigureOAuthTokenGeneration(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
var OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat("http://localhost:59822")
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
What am I doing wrong here?
Wow never mind, I dug into the full example given in the answer of the link. It seems that adding the extra fields isn't enough. You still have to add the parameters to the context yourself by overriding the TokenEndpoint function:
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}
I have followed the tutorials up till this point in the series. I am using one project in the solution that acts as both the token issuing authority as well as the resource server.
The JWT is generated using the endpoint mentioned in the startup class and I validated it on jwt.io as well. However when I pass this JWT using Postman on Chrome to the resource API end point secured with an Authorize attribute, I always find it returning
{
"message": "Authorization has been denied for this request." }
The other api method as in the api controller class below works when called thru Postman on Chrome.
I have used the latest versions of all dlls required from the nuget console
Code in the startup class
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuthTokenGeneration(app);
ConfigureOAuthTokenConsumption(app);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
private void ConfigureOAuthTokenGeneration(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat(ConfigurationManager.AppSettings["Issuer"]),
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
}
private void ConfigureOAuthTokenConsumption(IAppBuilder app)
{
string issuer = ConfigurationManager.AppSettings["Issuer"];
string audienceId = ConfigurationManager.AppSettings["AudienceId"];
byte[] audienceSecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["AudienceSecret"]);
// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audienceId },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
}
});
}
Code in the Custom OAuthProvider
public class CustomOAuthProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
return Task.FromResult<object>(null);
}
public override Task MatchEndpoint(OAuthMatchEndpointContext context)
{
//avoid pre-flight calls
if (context.OwinContext.Request.Method == "OPTIONS" && context.IsTokenEndpoint)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Methods", new[] { "POST" });
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Headers", new[] { "accept", "authorization", "content-type" });
context.OwinContext.Response.StatusCode = 200;
context.RequestCompleted();
return Task.FromResult<object>(null);
}
return base.MatchEndpoint(context);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
//setting up claims in the constructor of class UserDetails
UserDetails user = new UserDetails();
user.UserName = context.UserName;
user.FirstName = "Dummy First";
user.LastName = "Dummy Last";
ClaimsIdentity identity = new ClaimsIdentity("JWT-BearerAuth-Test");
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
foreach (string claim in user.Claims)
{
identity.AddClaim(new Claim(ClaimTypes.Role, claim));
}
var ticket = new AuthenticationTicket(identity, null);
context.Validated(ticket);
}
}
The custom JWT class
public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket>
{
private readonly string _issuer = string.Empty;
public CustomJwtFormat(string issuer)
{
_issuer = issuer;
}
public string Protect(AuthenticationTicket data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
string audienceId = ConfigurationManager.AppSettings["AudienceId"];
string symmetricKeyAsBase64 = ConfigurationManager.AppSettings["AudienceSecret"];
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
var signingKey = new HmacSigningCredentials(keyByteArray);
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);
var handler = new JwtSecurityTokenHandler();
var jwt = handler.WriteToken(token);
return jwt;
}
}
The Resource server's Api controller
public class AdminController : ApiController
{
//This call works
public IHttpActionResult ReadData(string id)
{
return Ok("ID sent in:" + id);
}
//[Authorize(Roles="EditRecord")] //doesnt work
[Authorize] //doesnt work either
public IHttpActionResult EditData(string id)
{
return Ok("Edited ID:" + id);
}
}
My environment is VS2013 with Framework 4.5 using OAuth2 with Web Api 2. Please excuse the long post.
You need to make sure that values for issuer, audienceId, and audienceSecret used in method "ConfigureOAuthTokenConsumption" are the same values used when you generated the JWT token, take care of trailing slashes "/".
This is the only thing comes to my mind right now.