I'm doing user-controlled using JWT token.
No problem logging in, but how to log out?
Comparison of the database
my opinion,
do you have better method suggestions
Token controller
I validate the user in UyelikOnaylama class
public class TokenController : ApiController
{
[HttpPost]
public async Task<HttpResponseMessage> Post(TokenRequestDto dto)
{
UyelikOnaylama uyelikOnaylama = new UyelikOnaylama();
var sonuc = await uyelikOnaylama.AsekronMethod(dto);
Random random = new Random();
if (sonuc==1)
{
var claims = new[]
{
new Claim(ClaimTypes.Name, dto.UserName),
new Claim(ClaimTypes.Role, random.ToString()+"asd"),
new Claim("scope", random.ToString()+"tasd"),
new Claim("scope", "**")
};
var token = new JwtSecurityToken(
issuer: "localhost",
audience: "localhost",
claims: claims,
expires: DateTime.UtcNow.AddMonths(30),
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes("!^'+sda1905SDASDQdqqdD'^+!34123")), SecurityAlgorithms.HmacSha256)
);
return Request.CreateResponse(HttpStatusCode.OK, new JwtSecurityTokenHandler().WriteToken(token));
}
else
{
return Request.CreateResponse(HttpStatusCode.Unauthorized, "Hatalı kullanıcı adı ya da parola");
}
}
}
I'm checking with a startup class
public class Startup
{
public void Configuration(IAppBuilder app)
{
string secretKey = "!^'+sda1905SDASDQdqqdD'^+!34123";
var opt = new JwtBearerAuthenticationOptions();
var prov = new SymmetricKeyIssuerSecurityKeyProvider[1];
prov[0] = new SymmetricKeyIssuerSecurityKeyProvider("localhost", Encoding.UTF8.GetBytes(secretKey));
opt.IssuerSecurityKeyProviders = prov;
opt.AllowedAudiences = new String[1] { "localhost" };
app.UseJwtBearerAuthentication(opt);
}
}
Correct me if I am wrong, but you get an AccessToken from your JWT Service? With this Token you get the desired rights for accessing data on the WebApi (or whatever you do with it). If your User logs out, the AccessToken is still available.
If this is your problem, simply remove the token from the List holding all of them. Also you can reduce the time, when the Token expires
Solution
Add this 3 classes into your project
public static class JwtSecurityKey
{
public static SymmetricSecurityKey Create(string secret)
{
return new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secret));
}
}
public sealed class JwtToken
{
private JwtSecurityToken Token;
internal JwtToken(JwtSecurityToken token)
{
this.Token = token;
}
public DateTime ValidTo => Token.ValidTo;
public string Value => new JwtSecurityTokenHandler().WriteToken(this.Token);
}
public class JwtTokenBuilder
{
private SecurityKey SecurityKey = null;
private string Subject = "";
private string Issuer = "";
private string Audience = "";
private Dictionary<string, string> Claims = new Dictionary<string, string>();
private int ExpiryInMinutes = 5;
public JwtTokenBuilder AddSecurityKey(SecurityKey securityKey)
{
this.SecurityKey = securityKey;
return this;
}
public JwtTokenBuilder AddSubject(string subject)
{
this.Subject = subject;
return this;
}
public JwtTokenBuilder AddIssuer(string issuer)
{
this.Issuer = issuer;
return this;
}
public JwtTokenBuilder AddAudience(string audience)
{
this.Audience = audience;
return this;
}
public JwtTokenBuilder AddClaim(string type, string value)
{
this.Claims.Add(type, value);
return this;
}
public JwtTokenBuilder AddClaims(Dictionary<string, string> claims)
{
this.Claims.Union(claims);
return this;
}
public JwtTokenBuilder AddExpiry(int expiryInMinutes)
{
this.ExpiryInMinutes = expiryInMinutes;
return this;
}
public JwtToken Build()
{
EnsureArguments();
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, this.Subject),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
}
.Union(this.Claims.Select(item => new Claim(item.Key, item.Value)));
var token = new JwtSecurityToken(
issuer: this.Issuer,
audience: this.Audience,
claims: claims,
expires: DateTime.UtcNow.AddMinutes(ExpiryInMinutes),
signingCredentials: new SigningCredentials(
this.SecurityKey,
SecurityAlgorithms.HmacSha256));
return new JwtToken(token);
}
#region Privates
private void EnsureArguments()
{
if (this.SecurityKey == null)
throw new ArgumentNullException("Security Key");
if (string.IsNullOrEmpty(this.Subject))
throw new ArgumentNullException("Subject");
if (string.IsNullOrEmpty(this.Issuer))
throw new ArgumentNullException("Issuer");
if (string.IsNullOrEmpty(this.Audience))
throw new ArgumentNullException("Audience");
}
#endregion
}
And in your Startup class, call this method:
private void ConfigureTokenServices(IServiceCollection services)
{
// Add Token Authentication
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters =
new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "Custom.Security.Bearer",
ValidAudience = "Custom.Security.Bearer",
IssuerSigningKey = JwtSecurityKey.Create("Yout securitykey which must be a very long string to work")
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
Debug.WriteLine("OnAuthenticationFailed: " + context.Exception.Message);
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
Debug.WriteLine("OnTokenValidated: " + context.SecurityToken);
return Task.CompletedTask;
}
};
});
services.AddAuthorization(options =>
{
options.AddPolicy("Guest",
policy => policy.RequireClaim("Role", "Add here your roles"));
});
}
And add this line
app.UseAuthentication();
to
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
With this you can then filter inside your Controller, which is the sense of JWT:
[Produces("application/json")]
[Route("YourRoute")]
[Authorize("Role")]
public class MyController
{
or you can do this directly on a method.
[Authorize("Role")]
[HttpGet, Route("YourRoute")]
public IActionResult HttpGet()
{
Tell me if I understood your issue correct and if there are any problems implementing this
Related
I have a few API endpoints (.net core mvc) that will use the logged-in user (JWT) if the user is logged in, but anyone is allowed to call the method.
By using [AllowAnonymous] together with [Authorize] I get the functionality I'm looking for but if the sent JWT is expired, I'm not getting a 401, but instead it is treated as an anonymous request.
I need the authorization logic to treat the endpoint as it was [Authorize] only if there is a Authorization: Bearer header, meaning that if the token is expired, it should return a 401
This functionality is only needed on a few endpoints and not on a full controller
I've tried the combination of [AllowAnonymous] + [Authorize].
Also tried RequireAssertion when creating the policy, but doesn't seem to have been meant for this
The method I'm using for testing:
[HttpPost]
[Route("testToken")]
[AllowAnonymous]
[Authorize(Policy = AuthFilterConvension.POLICY_AUTHORIZE_WHEN_HAS_BEARER)]
public async Task<IActionResult> testToken()
{
var user = await _signInManager.UserManager.GetUserAsync(HttpContext.User);
return Ok(new {result = user});
}
Setting up auth to support both cookies + JWT:
services.AddAuthorization(o =>
{
o.AddPolicy(AuthFilterConvension.POLICY_AUTHORIZE_WHEN_HAS_BEARER, b =>
{
b.RequireRole("Admin");
b.RequireAuthenticatedUser();
b.AuthenticationSchemes = new List<string> {JwtBearerDefaults.AuthenticationScheme};
});
});
services.AddAuthentication()
.AddCookie()
.AddJwtBearer(cfg =>
{
var issuer = Environment.GetEnvironmentVariable("JWT_ISSUER");
var tokenKey = Environment.GetEnvironmentVariable("JWT_TOKEN_KEY");
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters
{
RequireExpirationTime = true,
RequireSignedTokens = true,
ValidateAudience = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidIssuer = issuer,
ValidAudience = issuer,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenKey))
};
}
);
I would expect the [Authorize] header to return 401 Unauthorized when using expired tokens instead of calling the method as anonymous. Is this possible to set up?
Update
I created the attribute like this:
public class AuthorizeBearerWhenPresent : ActionFilterAttribute, IAsyncAuthorizationFilter
{
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
var headers = context.HttpContext.Request;
//TODO: Test header
var httpContext = context.HttpContext;
var authService = httpContext.RequestServices.GetRequiredService<IAuthorizationService>();
var authResult = await authService.AuthorizeAsync(httpContext.User, context,
AuthFilterConvension.POLICY_AUTHORIZE_WHEN_HAS_BEARER);
if (!authResult.Succeeded)
{
context.Result = new UnauthorizedResult();
}
}
}
However, no matter what I'm sending to the authService it returns true. Doesn't matter if I send an invalid JWT header or no header. Shouldn't this be the correct way to get executed a policy?
Thank you for your suggestions. While I was implementing the solution posted by Bart van der Drift, I stumbled upon the combination of a custom header together with using the IAuthorizationPolicyProvider
This way I'm using a custom policy name which I then overrides:
The constants in AuthFilterConvension:
public const string POLICY_JWT = "jwtPolicy";
public const string POLICY_AUTHORIZE_WHEN_HAS_BEARER = "authorizeWhenHasBearer";
Setting up the policies:
services.AddAuthorization(o =>
{
o.AddPolicy(AuthFilterConvension.POLICY_JWT, b =>
{
b.RequireRole("Admin");
b.RequireAuthenticatedUser();
b.AuthenticationSchemes = new List<string> {JwtBearerDefaults.AuthenticationScheme};
});
});
Add custom attribute:
public class AuthorizeBearerWhenPresent : AuthorizeAttribute
{
public AuthorizeBearerWhenPresent()
{
Policy = AuthFilterConvension.POLICY_AUTHORIZE_WHEN_HAS_BEARER;
}
}
The name POLICY_AUTHORIZE_WHEN_HAS_BEARER is not configured, but only used as a key in my CustomPolicyProvicer:
public class CustomPolicyProvider : IAuthorizationPolicyProvider
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly DefaultAuthorizationPolicyProvider _fallbackPolicyProvider;
public CustomPolicyProvider(IHttpContextAccessor httpContextAccessor, IOptions<AuthorizationOptions> options)
{
_httpContextAccessor = httpContextAccessor;
_fallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
}
public async Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
if (AuthFilterConvension.POLICY_AUTHORIZE_WHEN_HAS_BEARER.Equals(policyName))
{
if (_httpContextAccessor.HttpContext.Request.Headers.ContainsKey("Authorization"))
{
return await _fallbackPolicyProvider.GetPolicyAsync(AuthFilterConvension.POLICY_JWT);
}
return new AuthorizationPolicyBuilder()
.RequireAssertion(x=>true)
.Build();
}
return await _fallbackPolicyProvider.GetPolicyAsync(policyName);
}
public async Task<AuthorizationPolicy> GetDefaultPolicyAsync()
{
return await _fallbackPolicyProvider.GetDefaultPolicyAsync();
}
}
This way I can avoid doing custom handling of the JWT tokens
The following:
return new AuthorizationPolicyBuilder()
.RequireAssertion(x=>true)
.Build();
Is only used as a dummy "allow all"
You can create your own authentication logic by implementing the IAuthenticationFilter interface and inherit from ActionFilterAttribute:
public class MyCustomAuthentication : ActionFilterAttribute, System.Web.Http.Filters.IAuthenticationFilter
{
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
HttpRequestMessage request = context.Request;
AuthenticationHeaderValue authorization = request.Headers.Authorization;
// Handle the authorization header
}
}
Then in your controller, you can add the attribute either to the class or to specific methods.
[MyCustomAuthentication]
public async Task<IHttpActionResult> DoSomethingAsync()
{
// ...
}
If the token is present, you will also have to manually validate the token.
The code below is based on .Net Framework, so not sure if this also works for Core.
// Build URL based on your AAD-TenantId
var stsDiscoveryEndpoint = String.Format(CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}/.well-known/openid-configuration", "<Your_tenant_ID>");
// Get tenant information that's used to validate incoming jwt tokens
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint);
// Get Config from AAD:
var config = await configManager.GetConfigurationAsync();
// Validate token:
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
ValidAudience = "<Client_ID>",
ValidIssuer = "<Issuer>",
IssuerSigningTokens = config.SigningTokens,
CertificateValidator = X509CertificateValidator.ChainTrust
};
var parsedToken = (System.IdentityModel.Tokens.SecurityToken)new JwtSecurityToken();
try
{
tokenHandler.ValidateToken(token, validationParameters, out parsedToken);
result.ValidatedToken = (JwtSecurityToken)parsedToken;
}
catch (System.IdentityModel.Tokens.SecurityTokenValidationException stve)
{
// Handle error using stve.Message
}
Some more info and examples here.
You Could write a custom middleware to do so for example:
private readonly RequestDelegate _next;
public MyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext, IConfiguration configuration)
{
if (httpContext.Request.Headers.ContainsKey("Authorization"))
{
var authorizationToken = httpContext.Request.Headers["Authorization"].ToString();
if (!authorizationToken.StartsWith("bearer", StringComparison.OrdinalIgnoreCase))
{
await UnauthorizedResponseAsync(httpContext);
}
else
{
var token =authorizationToken.Substring("Bearer".Length).Trim())
if (httpContext.Request.Path == "Some of your path")
{
// DO your stuff
await _next.Invoke(httpContext);
}
}
}
else
{
await UnauthorizedResponseAsync(httpContext);
}
}
private static async Task UnauthorizedResponseAsync(HttpContext httpContext)
{
httpContext.Response.StatusCode = 401;
await httpContext.Response.WriteAsync("Unauthorized");
return;
}
I'm currently working on an OWIN OAuth implementation which uses JWT and supports token refreshing. I'm having intermittent problems with the token refresh process. The process works reliably on my development environment, but when published onto our Azure Service Fabric test environment, which is setup in a 3-node load-balanced configuration, the refresh token request often fails (not always!), and I get the infamous "invalid_grant" error.
I've found that the refresh token works successfully when being handled by the same service fabric node that issued it originally. However, it always fails when handled by a different node.
My understanding is that by using JWT, having a micro-service infrastructure deliver a load-balanced authentication server get's around the "machine-key" related issues that arise from using the OOTB access token format provided by OWIN.
Failed refresh tokens are making their way into the IAuthenticationTokenProvider.ReceiveAsync method, but the OAuthAuthorizationServerProvider.GrantRefreshToken method is never being hit, suggesting something in the OWIN middle-ware is not happy with the refresh token. Can anyone offer any insight into what the cause may be?
Now for the code, there's quite a bit - apologies for all the reading!
The authentication server is a service fabric stateless service, here's the ConfigureApp method:
protected override void ConfigureApp(IAppBuilder appBuilder)
{
appBuilder.UseCors(CorsOptions.AllowAll);
var oAuthAuthorizationServerOptions = InjectionContainer.GetInstance<OAuthAuthorizationServerOptions>();
appBuilder.UseOAuthAuthorizationServer(oAuthAuthorizationServerOptions);
appBuilder.UseJwtBearerAuthentication(InjectionContainer.GetInstance<JwtBearerAuthenticationOptions>());
appBuilder.UseWebApi(GetHttpConfiguration(InjectionContainer));
}
Here's the implementation of OAuthAuthorizationServerOptions:
public class AppOAuthOptions : OAuthAuthorizationServerOptions
{
public AppOAuthOptions(IAppJwtConfiguration configuration,
IAuthenticationTokenProvider authenticationTokenProvider,
IOAuthAuthorizationServerProvider authAuthorizationServerProvider)
{
AllowInsecureHttp = true;
TokenEndpointPath = "/token";
AccessTokenExpireTimeSpan = configuration.ExpirationMinutes;
AccessTokenFormat = new AppJwtWriterFormat(this, configuration);
Provider = authAuthorizationServerProvider;
RefreshTokenProvider = authenticationTokenProvider;
}
}
And here's the JwtBearerAuthenticationOptions implementation:
public class AppJwtOptions : JwtBearerAuthenticationOptions
{
public AppJwtOptions(IAppJwtConfiguration config)
{
AuthenticationMode = AuthenticationMode.Active;
AllowedAudiences = new[] {config.JwtAudience};
IssuerSecurityTokenProviders = new[]
{
new SymmetricKeyIssuerSecurityTokenProvider(
config.JwtIssuer,
Convert.ToBase64String(Encoding.UTF8.GetBytes(config.JwtKey)))
};
}
}
public class InMemoryJwtConfiguration : IAppJwtConfiguration
{
AppSettings _appSettings;
public InMemoryJwtConfiguration(AppSettings appSettings)
{
_appSettings = appSettings;
}
public int ExpirationMinutes
{
get { return 15; }
set { }
}
public string JwtAudience
{
get { return "CENSORED AUDIENCE"; }
set { }
}
public string JwtIssuer
{
get { return "CENSORED ISSUER"; }
set { }
}
public string JwtKey
{
get { return "CENSORED KEY :)"; }
set { }
}
public int RefreshTokenExpirationMinutes
{
get { return 60; }
set { }
}
public string TokenPath
{
get { return "/token"; }
set { }
}
}
And the ISecureData implementation:
public class AppJwtWriterFormat : ISecureDataFormat<AuthenticationTicket>
{
public AppJwtWriterFormat(
OAuthAuthorizationServerOptions options,
IAppJwtConfiguration configuration)
{
_options = options;
_configuration = configuration;
}
public string Protect(AuthenticationTicket data)
{
if (data == null)
throw new ArgumentNullException(nameof(data));
var now = DateTime.UtcNow;
var expires = now.AddMinutes(_options.AccessTokenExpireTimeSpan.TotalMinutes);
var symmetricKey = Encoding.UTF8.GetBytes(_configuration.JwtKey);
var signingCredentials = new SigningCredentials(
new InMemorySymmetricSecurityKey(symmetricKey),
SignatureAlgorithm, DigestAlgorithm);
var token = new JwtSecurityToken(
_configuration.JwtIssuer,
_configuration.JwtAudience,
data.Identity.Claims,
now,
expires,
signingCredentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
public AuthenticationTicket Unprotect(string protectedText)
{
throw new NotImplementedException();
}
}
This is the IAuthenticationTokenProvider implementation:
public class RefreshTokenProvider : IAuthenticationTokenProvider
{
private readonly IAppJwtConfiguration _configuration;
private readonly IContainer _container;
public RefreshTokenProvider(IAppJwtConfiguration configuration, IContainer container)
{
_configuration = configuration;
_container = container;
_telemetry = telemetry;
}
public void Create(AuthenticationTokenCreateContext context)
{
CreateAsync(context).Wait();
}
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
try
{
var refreshTokenId = Guid.NewGuid().ToString("n");
var now = DateTime.UtcNow;
using (var container = _container.GetNestedContainer())
{
var hashLogic = container.GetInstance<IHashLogic>();
var tokenStoreLogic = container.GetInstance<ITokenStoreLogic>();
var userName = context.Ticket.Identity.FindFirst(ClaimTypes.UserData).Value;
var userToken = new UserToken
{
Email = userName,
RefreshTokenIdHash = hashLogic.HashInput(refreshTokenId),
Subject = context.Ticket.Identity.Name,
RefreshTokenExpiresUtc =
now.AddMinutes(Convert.ToDouble(_configuration.RefreshTokenExpirationMinutes)),
AccessTokenExpirationDateTime =
now.AddMinutes(Convert.ToDouble(_configuration.ExpirationMinutes))
};
context.Ticket.Properties.IssuedUtc = now;
context.Ticket.Properties.ExpiresUtc = userToken.RefreshTokenExpiresUtc;
context.Ticket.Properties.AllowRefresh = true;
userToken.RefreshToken = context.SerializeTicket();
await tokenStoreLogic.CreateUserTokenAsync(userToken);
context.SetToken(refreshTokenId);
}
}
catch (Exception ex)
{
// exception logging removed for brevity
throw;
}
}
public void Receive(AuthenticationTokenReceiveContext context)
{
ReceiveAsync(context).Wait();
}
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
try
{
using (var container = _container.GetNestedContainer())
{
var hashLogic = container.GetInstance<IHashLogic>();
var tokenStoreLogic = container.GetInstance<ITokenStoreLogic>();
var hashedTokenId = hashLogic.HashInput(context.Token);
var refreshToken = await tokenStoreLogic.FindRefreshTokenAsync(hashedTokenId);
if (refreshToken == null)
{
return;
}
context.DeserializeTicket(refreshToken.RefreshToken);
await tokenStoreLogic.DeleteRefreshTokenAsync(hashedTokenId);
}
}
catch (Exception ex)
{
// exception logging removed for brevity
throw;
}
}
}
And finally, this is the OAuthAuthorizationServerProvider implementation:
public class AppOAuthProvider : OAuthAuthorizationServerProvider
{
public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
if (context.ClientId != null)
{
context.Rejected();
return Task.FromResult(0);
}
// Change authentication ticket for refresh token requests
var newIdentity = new ClaimsIdentity(context.Ticket.Identity);
newIdentity.AddClaim(new Claim("newClaim", "refreshToken"));
var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
context.Validated(newTicket);
return Task.FromResult(0);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
using (var container = _container.GetNestedContainer())
{
var requestedAuthenticationType = context.Request.Query["type"];
var requiredAuthenticationType = (int)AuthenticationType.None;
if (string.IsNullOrEmpty(requestedAuthenticationType) || !int.TryParse(requestedAuthenticationType, out requiredAuthenticationType))
{
context.SetError("Authentication Type Missing", "Type parameter is required to check which type of user you are trying to authenticate with.");
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
return;
}
var authenticationWorker = GetInstance<IAuthenticationWorker>(container);
var result = await authenticationWorker.AuthenticateAsync(new AuthenticationRequestViewModel
{
UserName = context.UserName,
Password = context.Password,
IpAddress = context.Request.RemoteIpAddress ?? "",
UserAgent = context.Request.Headers.ContainsKey("User-Agent") ? context.Request.Headers["User-Agent"] : ""
});
if (result.SignInStatus != SignInStatus.Success)
{
context.SetError(result.SignInStatus.ToString(), result.Message);
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
return;
}
// After we have successfully logged in. Check the authentication type for the just authenticated user
var userAuthenticationType = (int)result.AuthenticatedUserViewModel.Type;
// Check if the auth types match
if (userAuthenticationType != requiredAuthenticationType)
{
context.SetError("Invalid Account", "InvalidAccountForPortal");
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
return;
}
var identity = SetClaimsIdentity(context, result.AuthenticatedUserViewModel);
context.Validated(identity);
}
}
public override async Task TokenEndpointResponse(OAuthTokenEndpointResponseContext context)
{
using (var container = GetNestedContainer())
{
var email = context.Identity.FindFirst(ClaimTypes.UserData).Value;
var accessTokenHash = _hashLogic.HashInput(context.AccessToken);
var tokenStoreLogic = GetInstance<ITokenStoreLogic>(container);
await tokenStoreLogic.UpdateUserTokenAsync(email, accessTokenHash);
var authLogic = GetInstance<IAuthenticationLogic>(container);
var userDetail = await authLogic.GetDetailsAsync(email);
context.AdditionalResponseParameters.Add("user_id", email);
context.AdditionalResponseParameters.Add("user_name", userDetail.Name);
context.AdditionalResponseParameters.Add("user_known_as", userDetail.KnownAs);
context.AdditionalResponseParameters.Add("authentication_type", userDetail.Type);
}
await base.TokenEndpointResponse(context);
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
return Task.FromResult(0);
}
private ClaimsIdentity SetClaimsIdentity(OAuthGrantResourceOwnerCredentialsContext context, AuthenticatedUserViewModel user)
{
var identity = new ClaimsIdentity(
new[]
{
new Claim(ClaimTypes.Name, context.UserName),
new Claim(ClaimTypes.SerialNumber, user.SerialNumber),
new Claim(ClaimTypes.UserData, user.Email.ToString(CultureInfo.InvariantCulture)),
new Claim(ClaimTypeUrls.AdminScope, user.Scope.ToString()),
new Claim(ClaimTypeUrls.DriverId, user.DriverId.ToString(CultureInfo.InvariantCulture)),
new Claim(ClaimTypeUrls.AdministratorId, user.AdministratorId.ToString(CultureInfo.InvariantCulture))
},
_authenticationType
);
//add roles
var roles = user.Roles;
foreach (var role in roles)
identity.AddClaim(new Claim(ClaimTypes.Role, role));
return identity;
}
}
I am stumped by this behavior. Basically, when a refresh of a token is requested, WebAPI goes through the logic of removing the old refresh_token from the data, and I don't see any errors in the debugger. However, the API returns a HTTP 400 with "invalid_grant" as the error.
Startup.cs
// OAuth Server configuration
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = false,
TokenEndpointPath = new PathString("/oauth2/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(1),
AccessTokenFormat = new AccessTokenJwtFormat(issuer),
RefreshTokenProvider = new SimpleRefreshTokenProvider(),
RefreshTokenFormat = new RefreshTokenJwtFormat(issuer),
Provider = new CustomOAuthProvider()
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
SimpleRefreshTokenProvider.cs
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var clientid = context.Ticket.Properties.Dictionary["as:client_id"];
if (string.IsNullOrEmpty(clientid))
{
return;
}
var refreshTokenId = Guid.NewGuid().ToString("n");
var refreshTokenLifeTime = context.OwinContext.Get<string>("as:clientRefreshTokenLifeTime");
var _repo = ClientDbProvider.GetInstance();
var token = new RefreshToken()
{
Id = ClientHelper.GetHash(refreshTokenId),
ClientId = clientid,
Subject = context.Ticket.Identity.Name,
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifeTime))
};
context.Ticket.Properties.IssuedUtc = new DateTimeOffset(token.IssuedUtc);
context.Ticket.Properties.ExpiresUtc = new DateTimeOffset(token.ExpiresUtc);
context.Ticket.Properties.Dictionary.Add("refreshTokenId", refreshTokenId);
context.Ticket.Identity.AddClaim(new Claim(ClaimTypes.Role, "refreshToken"));
token.ProtectedTicket = context.SerializeTicket();
var result = await _repo.AddRefreshToken(token);
if (result)
{
context.SetToken(token.ProtectedTicket);
}
return;
}
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
var _repo = ClientDbProvider.GetInstance();
string hashedTokenId = ClientHelper.GetHash(context.Token);
var refreshToken = await _repo.FindRefreshToken(hashedTokenId);
if (refreshToken != null)
{
//Get protectedTicket from refreshToken class
context.DeserializeTicket(refreshToken.ProtectedTicket);
var result = await _repo.RemoveRefreshToken(hashedTokenId);
}
}
public void Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
}
public void Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
}
RefreshTokenJwtFormat.cs
public class RefreshTokenJwtFormat : ISecureDataFormat<AuthenticationTicket>
{
private const string AudiencePropertyKey = "audience";
private readonly string _issuer = string.Empty;
public RefreshTokenJwtFormat(string issuer)
{
_issuer = issuer;
audStore = new AudiencesStore();
}
public string Protect(AuthenticationTicket data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
string audienceId = data.Properties.Dictionary.ContainsKey(AudiencePropertyKey) ? data.Properties.Dictionary[AudiencePropertyKey] : null;
if (string.IsNullOrWhiteSpace(audienceId)) throw new InvalidOperationException("AuthenticationTicket.Properties does not include audience");
var audience = GetAudience(audienceId);
string symmetricKeyAsBase64 = audience.Base64Secret;
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
var signingCredentials = new SigningCredentials(
new InMemorySymmetricSecurityKey(keyByteArray),
SecurityAlgorithms.HmacSha256Signature,
SecurityAlgorithms.Sha256Digest);
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
var payload = new JwtPayload(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime);
if (data.Properties.Dictionary.ContainsKey("refreshTokenId"))
{
payload.Add("refreshTokenId", data.Properties.Dictionary["refreshTokenId"]);
}
var header = new JwtHeader(signingCredentials);
var token = new JwtSecurityToken(header, payload);
var handler = new JwtSecurityTokenHandler();
var jwt = handler.WriteToken(token);
return jwt;
}
public AuthenticationTicket Unprotect(string protectedText)
{
var handler = new JwtSecurityTokenHandler();
SecurityToken securityToken = handler.ReadToken(protectedText);
var audienceId = ((JwtSecurityToken)securityToken).Claims.First(x => x.Type == "aud").Value;
var audience = GetAudience(audienceId);
string symmetricKeyAsBase64 = audience.Base64Secret;
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
var securityKey = new InMemorySymmetricSecurityKey(keyByteArray);
var validationParameters = new TokenValidationParameters()
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = securityKey,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
ValidateAudience = true,
ValidAudience = audienceId,
ValidateIssuer = true,
ValidIssuer = _issuer
};
SecurityToken validatedToken;
ClaimsPrincipal principal = null;
try
{
principal = handler.ValidateToken(protectedText, validationParameters, out validatedToken);
}
catch(Exception ex)
{
return null;
}
return new AuthenticationTicket(principal.Identities.First(), new AuthenticationProperties());
}
private Models.Audience GetAudience(string audienceId)
{
var findAudTask = Task.Run(() => audStore.FindAudienceAsync(audienceId));
findAudTask.Wait();
var audience = findAudTask.Result;
return audience;
}
private AudiencesStore audStore;
}
First you need
implement GrantRefreshToken(OAuthGrantRefreshTokenContext context) in your CustomOAuthProvider
something like
public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
// Change auth ticket for refresh token requests
var newIdentity = new ClaimsIdentity(context.Ticket.Identity);
//newIdentity.AddClaim(new Claim("newClaim", "newValue"));
var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
context.Validated(newTicket);
return Task.FromResult<object>(null);
}
Second you need to change your method Unprotect in RefreshTokenJwtFormat.
replace
return new AuthenticationTicket(principal.Identities.First(), new AuthenticationProperties());
with
return new AuthenticationTicket(principal.Identities.First(), new AuthenticationProperties
{
IssuedUtc = validatedToken.ValidFrom,
ExpiresUtc = validatedToken.ValidTo
});
i have similar (working) code. I'm not a specialist on this but i compared my code a bit and noticed:
in SimpleRefreshToken.cs:
in receiveAsync, my code ends with:
context.SetTicket(context.Ticket);
return Task.FromResult<object>(null);
Which sets the ticket and return a value.
The same for createAsync, my code ends with:
return Task.FromResult<object>(null);
Allthough, no idea if this will help
I'm using Owin and UseWsFederationAuthentication() in AspNetCore MVC 1.0.0. app. Authentication works perfectly but i cant SignOut the user.
This code
public class AccountController : Controller
{
public async Task<IActionResult> SignOut()
{
await this.HttpContext.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationType);
return RedirectToAction("Index", "Test");
}
}
Throws:
InvalidOperationException: No authentication handler is configured to handle the scheme: Cookies
The HttpContext.Authentication is set to Microsoft.AspNetCore.Http.Authentication.Internal.DefaultAuthenticationManager
Startup.cs:Configure
app.UseOwinAppBuilder(builder =>
{
var authConfig = new WsFederationAuthenticationSettings
{
MetadataAddress = this.Configuration.GetValue<string>("Eyc.Sdk:Authentication:WsFederation:MetadataAddress"),
Realm = this.Configuration.GetValue<string>("Eyc.Sdk:Authentication:WsFederation:Realm"),
UseCookieSlidingExpiration = this.Configuration.GetValue<bool>("Eyc.Sdk:Authentication:WsFederation:UseCookieSlidingExpiration"),
CookieExpireTimeSpan = this.Configuration.GetValue<string>("Eyc.Sdk:Authentication:WsFederation:CookieExpireTimeSpan")
};
builder.UseEycAuthentication(authConfig, app.ApplicationServices);
});
public static class ApplicationBuilderExtensions
{
public static IApplicationBuilder UseOwinAppBuilder(this IApplicationBuilder app, Action<global::Owin.IAppBuilder> configuration)
{
return app.UseOwin(setup => setup(next =>
{
var builder = new AppBuilder();
var lifetime = (IApplicationLifetime)app.ApplicationServices.GetService(typeof(IApplicationLifetime));
var properties = new AppProperties(builder.Properties);
properties.AppName = app.ApplicationServices.GetApplicationUniqueIdentifier();
properties.OnAppDisposing = lifetime.ApplicationStopping;
properties.DefaultApp = next;
configuration(builder);
return builder.Build<Func<IDictionary<string, object>, Task>>();
}));
}
}
public static class AppBuilderExtensions
{
public static IAppBuilder UseEycAuthentication(
this IAppBuilder app,
WsFederationAuthenticationSettings authenticationSettings,
IServiceProvider serviceProvider,
bool authenticateEveryRequest = true)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
if (authenticationSettings == null)
{
throw new ArgumentNullException(nameof(authenticationSettings));
}
if (serviceProvider == null)
{
throw new ArgumentNullException(nameof(serviceProvider));
}
return app.ConfigureWsFederationAuthentication(serviceProvider, authenticationSettings, authenticateEveryRequest);
}
private static IAppBuilder ConfigureWsFederationAuthentication(
this IAppBuilder app,
IServiceProvider serviceProvider,
WsFederationAuthenticationSettings authenticationSettings,
bool authenticateEveryRequest = true)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(
new CookieAuthenticationOptions
{
SlidingExpiration = authenticationSettings.UseCookieSlidingExpiration,
ExpireTimeSpan = authenticationSettings.GetCookieExpireTimeSpan()
});
var wsFederationAuthenticationOptions = GetWsFederationAuthenticationOptions(authenticationSettings);
app.UseWsFederationAuthentication(wsFederationAuthenticationOptions);
var eycAuthenticationManager = (IEycAuthenticationManager)serviceProvider.GetService(typeof(IEycAuthenticationManager));
app.Use<EycAuthenticationOwinMiddleware>(eycAuthenticationManager);
// http://stackoverflow.com/questions/23524318/require-authentication-for-all-requests-to-an-owin-application
if (authenticateEveryRequest)
{
app.Use(async (owinContext, next) =>
{
var user = owinContext.Authentication.User;
if (!(user?.Identity?.IsAuthenticated ?? false))
{
owinContext.Authentication.Challenge();
return;
}
await next();
});
}
return app;
}
private static WsFederationAuthenticationOptions GetWsFederationAuthenticationOptions(WsFederationAuthenticationSettings settings)
{
var wsFederationAuthenticationNotifications = GetWsFederationAuthenticationNotifications(settings);
var wsFederationAuthenticationOptions = new WsFederationAuthenticationOptions
{
Wtrealm = settings.Realm,
MetadataAddress = settings.MetadataAddress,
TokenValidationParameters = new TokenValidationParameters
{
ValidAudiences = settings.Realms
},
Notifications = wsFederationAuthenticationNotifications
};
if (settings.UseCookieSlidingExpiration)
{
// this needs to be false for sliding expiration to work
wsFederationAuthenticationOptions.UseTokenLifetime = false;
}
return wsFederationAuthenticationOptions;
}
private static WsFederationAuthenticationNotifications GetWsFederationAuthenticationNotifications(WsFederationAuthenticationSettings settings)
{
var wsFederationAuthenticationNotifications = new WsFederationAuthenticationNotifications();
wsFederationAuthenticationNotifications.AuthenticationFailed = settings.AuthenticationFailed ?? wsFederationAuthenticationNotifications.AuthenticationFailed;
wsFederationAuthenticationNotifications.MessageReceived = settings.MessageReceived ?? wsFederationAuthenticationNotifications.MessageReceived;
wsFederationAuthenticationNotifications.RedirectToIdentityProvider = settings.RedirectToIdentityProvider ?? wsFederationAuthenticationNotifications.RedirectToIdentityProvider;
wsFederationAuthenticationNotifications.SecurityTokenReceived = settings.SecurityTokenReceived ?? wsFederationAuthenticationNotifications.SecurityTokenReceived;
wsFederationAuthenticationNotifications.SecurityTokenValidated = settings.SecurityTokenValidated ?? wsFederationAuthenticationNotifications.SecurityTokenValidated;
return wsFederationAuthenticationNotifications;
}
}
public class EycAuthenticationOwinMiddleware : OwinMiddleware
{
private readonly IEycAuthenticationManager _eycAuthenticationManager;
#region ctors
public EycAuthenticationOwinMiddleware(OwinMiddleware next, IEycAuthenticationManager eycAuthenticationManager)
: base(next)
{
if (eycAuthenticationManager == null)
{
throw new ArgumentNullException(nameof(eycAuthenticationManager));
}
this._eycAuthenticationManager = eycAuthenticationManager;
}
#endregion
public override Task Invoke(IOwinContext context)
{
if (context.Authentication.User != null)
{
context.Authentication.User =
this._eycAuthenticationManager.Authenticate(
context.Request.Uri.AbsoluteUri,
context.Authentication.User);
}
return this.Next.Invoke(context);
}
}
public class EycAuthenticationManager : ClaimsAuthenticationManager, IEycAuthenticationManager
{
private readonly IClaimsTransformer _claimsTransformer;
#region ctors
public EycAuthenticationManager(IClaimsTransformer claimsTransformer)
{
this._claimsTransformer = claimsTransformer;
}
#endregion
public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
{
if (incomingPrincipal != null && !incomingPrincipal.Identity.IsAuthenticated)
{
return base.Authenticate(resourceName, incomingPrincipal);
}
return this._claimsTransformer.TransformIdentity(incomingPrincipal);
}
}
public class ClaimsTransformer : IClaimsTransformer
{
public ClaimsPrincipal TransformIdentity(IPrincipal principal)
{
if (!(principal is ClaimsPrincipal))
{
throw new Exception("The provided IPrincipal object is not of type ClaimsPrincipal.");
}
var user = (ClaimsPrincipal)principal;
var claims = user.Claims.ToList();
if (claims.All(x => x.Type != ClaimTypes.Email))
{
var upnClaim = claims.FirstOrDefault(x => x.Type == ClaimTypes.Upn);
if (upnClaim != null)
{
claims.Add(new Claim(ClaimTypes.Email, upnClaim.Value));
}
}
return new ClaimsPrincipal(new ClaimsIdentity(claims, principal.Identity.AuthenticationType));
}
}
Here is the way how to SignOut using Owin context, app is "IAppBuilder":
app.Map("/signout", map =>
{
map.Run(ctx =>
{
ctx.Authentication.SignOut();
return Task.CompletedTask;
});
});
More details here: https://leastprivilege.com/2014/02/21/test-driving-the-ws-federation-authentication-middleware-for-katana/
UseWsFederationAuthentication will not work in AspNetCore MVC 1.0.0. UseWsFederationAuthentication used non-standard OWIN keys that are not supported by UseOwin, so it cannot communicate with MVC.
I am trying to figure out how to get the claim out of my token.
I will try an keep the explanation short
I have an HTML page that does a post to my web api, does and auth
check and returns an JWT token
when i get the token back i want to send it to different url, and the way i am doing it is using a querystring. I know i can use cookies but for this app we dont want to use them. So if my url looks like this http://somedomain/checkout/?token=bearer token comes here
I am using Owin middleware and this is what i have so far
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
Provider = new ApplicationOAuthBearerAuthenticationProvider(),
});
public class ApplicationOAuthBearerAuthenticationProvider
: OAuthBearerAuthenticationProvider
{
public override Task RequestToken(OAuthRequestTokenContext context)
{
if (context == null)
throw new ArgumentNullException("context");
var token = HttpContext.Current.Request.QueryString["token"];
if (!string.IsNullOrEmpty(token))
context.Token = token;
return Task.FromResult<object>(null);
}
}
But how do i get the Claims out of the Token or just check the IsAuthenticated
I tried the Following inside my controller just to check, but the IsAuthenticated is always false
var identity = (ClaimsIdentity) HttpContext.Current.GetOwinContext().Authentication.User.Identity;
if (!identity.IsAuthenticated)
return;
var id = identity.FindFirst(ClaimTypes.NameIdentifier);
OK so I managed to figure it out. The above code that I had is all working well but I needed to add the UseJwtBearerAuthentication middle ware.
One thing I did end up changing from my original code was i changed the context.Token = token; to context.Request.Headers.Add("Authorization", new[] { string.Format("Bearer {0}", token) });
So my startup class looks like this...
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
Provider = new ApplicationOAuthBearerAuthenticationProvider(),
});
app.UseJwtBearerAuthentication(JwtOptions());
ConfigureAuth(app);
}
private static JwtBearerAuthenticationOptions JwtOptions()
{
var key = Encoding.UTF8.GetBytes(ConfigurationManager.AppSettings["auth:key"]);
var jwt = new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = Some Audience,
ValidIssuer = Some Issuer,
IssuerSigningToken = new BinarySecretSecurityToken(key),
RequireExpirationTime = false,
ValidateLifetime = false
}
};
return jwt;
}
public class ApplicationOAuthBearerAuthenticationProvider
: OAuthBearerAuthenticationProvider
{
public override Task RequestToken(OAuthRequestTokenContext context)
{
if (context == null)
throw new ArgumentNullException("context");
var token = HttpContext.Current.Request.QueryString["token"];
if (!string.IsNullOrEmpty(token))
context.Request.Headers.Add("Authorization", new[] { string.Format("Bearer {0}", token) });
return Task.FromResult<object>(null);
}
}
}