1
0
mirror of https://github.com/bitwarden/server synced 2026-01-04 09:33:40 +00:00

remove deprecated jwt bearer authentication method

This commit is contained in:
Kyle Spearrin
2017-06-06 23:19:42 -04:00
parent 811bbbfe0a
commit d8c0994ed3
16 changed files with 29 additions and 624 deletions

View File

@@ -15,7 +15,7 @@ namespace Bit.Core.Identity
&& user.TwoFactorProvider.HasValue
&& user.TwoFactorProvider.Value == TwoFactorProviderType.Authenticator
&& !string.IsNullOrWhiteSpace(user.AuthenticatorKey);
return Task.FromResult(canGenerate);
}

View File

@@ -1,66 +0,0 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Authentication.JwtBearer;
namespace Bit.Core.Identity
{
public static class JwtBearerAppBuilderExtensions
{
public static IApplicationBuilder UseJwtBearerIdentity(this IApplicationBuilder app)
{
if(app == null)
{
throw new ArgumentNullException(nameof(app));
}
var marker = app.ApplicationServices.GetService<IdentityMarkerService>();
if(marker == null)
{
throw new InvalidOperationException("Must Call AddJwtBearerIdentity");
}
var jwtOptions = app.ApplicationServices.GetRequiredService<IOptions<JwtBearerIdentityOptions>>().Value;
var options = BuildJwtBearerOptions(jwtOptions);
app.UseJwtBearerAuthentication(options);
return app;
}
public static JwtBearerOptions BuildJwtBearerOptions(JwtBearerIdentityOptions jwtOptions)
{
var options = new JwtBearerOptions();
// Basic settings - signing key to validate with, audience and issuer.
options.TokenValidationParameters.IssuerSigningKey = jwtOptions.SigningCredentials.Key;
options.TokenValidationParameters.ValidAudience = jwtOptions.Audience;
options.TokenValidationParameters.ValidIssuer = jwtOptions.Issuer;
options.TokenValidationParameters.RequireExpirationTime = true;
options.TokenValidationParameters.RequireSignedTokens = false;
// When receiving a token, check that we've signed it.
options.TokenValidationParameters.RequireSignedTokens = false;
//// When receiving a token, check that it is still valid.
options.TokenValidationParameters.ValidateLifetime = true;
// This defines the maximum allowable clock skew - i.e. provides a tolerance on the token expiry time
// when validating the lifetime. As we're creating the tokens locally and validating them on the same
// machines which should have synchronised time, this can be set to zero. Where external tokens are
// used, some leeway here could be useful.
options.TokenValidationParameters.ClockSkew = TimeSpan.FromMinutes(0);
options.Events = new JwtBearerEvents
{
OnTokenValidated = JwtBearerEventImplementations.ValidatedTokenAsync,
OnAuthenticationFailed = JwtBearerEventImplementations.AuthenticationFailedAsync,
OnMessageReceived = JwtBearerEventImplementations.MessageReceivedAsync
};
return options;
}
}
}

View File

@@ -1,56 +0,0 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.IdentityModel.Tokens;
using Bit.Core.Services;
namespace Bit.Core.Identity
{
public static class JwtBearerEventImplementations
{
public async static Task ValidatedTokenAsync(TokenValidatedContext context)
{
if(context.HttpContext.RequestServices == null)
{
throw new InvalidOperationException("RequestServices is null");
}
var userService = context.HttpContext.RequestServices.GetRequiredService<IUserService>();
var signInManager = context.HttpContext.RequestServices.GetRequiredService<JwtBearerSignInManager>();
var userId = userService.GetProperUserId(context.Ticket.Principal);
var user = await userService.GetUserByIdAsync(userId.Value);
// validate security token
if(!await signInManager.ValidateSecurityStampAsync(user, context.Ticket.Principal))
{
throw new SecurityTokenValidationException("Bad security stamp.");
}
}
public static Task AuthenticationFailedAsync(AuthenticationFailedContext context)
{
if(!context.HttpContext.User.Identity.IsAuthenticated)
{
context.State = EventResultState.HandledResponse;
context.Ticket = new AuthenticationTicket(context.HttpContext.User, new AuthenticationProperties(),
context.Options.AuthenticationScheme);
}
return Task.FromResult(0);
}
public static Task MessageReceivedAsync(MessageReceivedContext context)
{
if(!context.Request.Headers.ContainsKey("Authorization"))
{
context.Token = context.Request.Query["access_token"];
}
return Task.FromResult(0);
}
}
}

View File

@@ -1,16 +0,0 @@
using System;
using Microsoft.IdentityModel.Tokens;
namespace Bit.Core.Identity
{
public class JwtBearerIdentityOptions
{
public string Audience { get; set; }
public string Issuer { get; set; }
public SigningCredentials SigningCredentials { get; set; }
public TimeSpan? TokenLifetime { get; set; }
public TimeSpan? TwoFactorTokenLifetime { get; set; }
public string AuthenticationMethod { get; set; } = "Application";
public string TwoFactorAuthenticationMethod { get; set; } = "TwoFactor";
}
}

View File

@@ -1,58 +0,0 @@
using System;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Bit.Core.Models.Table;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace Bit.Core.Identity
{
public static class JwtBearerIdentityServiceCollectionExtensions
{
public static IdentityBuilder AddJwtBearerIdentity(
this IServiceCollection services)
{
return services.AddJwtBearerIdentity(setupAction: null, jwtBearerSetupAction: null);
}
public static IdentityBuilder AddJwtBearerIdentity(
this IServiceCollection services,
Action<IdentityOptions> setupAction,
Action<JwtBearerIdentityOptions> jwtBearerSetupAction)
{
// Services used by identity
services.AddOptions();
services.AddAuthentication();
// Hosting doesn't add IHttpContextAccessor by default
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// Identity services
services.TryAddSingleton<IdentityMarkerService>();
services.TryAddScoped<IUserValidator<User>, UserValidator<User>>();
services.TryAddScoped<IPasswordValidator<User>, PasswordValidator<User>>();
services.TryAddScoped<IPasswordHasher<User>, PasswordHasher<User>>();
services.TryAddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>();
services.TryAddScoped<IRoleValidator<Role>, RoleValidator<Role>>();
// No interface for the error describer so we can add errors without rev'ing the interface
services.TryAddScoped<IdentityErrorDescriber>();
services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<User>>();
services.TryAddScoped<IUserClaimsPrincipalFactory<User>, UserClaimsPrincipalFactory<User, Role>>();
services.TryAddScoped<UserManager<User>, UserManager<User>>();
services.TryAddScoped<JwtBearerSignInManager, JwtBearerSignInManager>();
services.TryAddScoped<RoleManager<Role>, RoleManager<Role>>();
if(setupAction != null)
{
services.Configure(setupAction);
}
if(jwtBearerSetupAction != null)
{
services.Configure(jwtBearerSetupAction);
}
return new IdentityBuilder(typeof(User), typeof(Role), services);
}
}
}

View File

@@ -1,195 +0,0 @@
using System;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Bit.Core.Models.Table;
using Microsoft.AspNetCore.Builder;
using Microsoft.IdentityModel.Tokens;
using Bit.Core.Repositories;
using Bit.Core.Services;
namespace Bit.Core.Identity
{
public class JwtBearerSignInManager
{
private readonly IDeviceRepository _deviceRepository;
private readonly IDeviceService _deviceService;
public JwtBearerSignInManager(
UserManager<User> userManager,
IHttpContextAccessor contextAccessor,
IUserClaimsPrincipalFactory<User> claimsFactory,
IOptions<IdentityOptions> optionsAccessor,
IOptions<JwtBearerIdentityOptions> jwtIdentityOptionsAccessor,
IOptions<JwtBearerOptions> jwtOptionsAccessor,
ILogger<JwtBearerSignInManager> logger,
IDeviceRepository deviceRepository,
IDeviceService deviceService)
{
UserManager = userManager;
Context = contextAccessor.HttpContext;
ClaimsFactory = claimsFactory;
IdentityOptions = optionsAccessor?.Value ?? new IdentityOptions();
JwtIdentityOptions = jwtIdentityOptionsAccessor?.Value ?? new JwtBearerIdentityOptions();
JwtBearerOptions = jwtOptionsAccessor?.Value ?? new JwtBearerOptions();
_deviceRepository = deviceRepository;
_deviceService = deviceService;
}
internal UserManager<User> UserManager { get; set; }
internal HttpContext Context { get; set; }
internal IUserClaimsPrincipalFactory<User> ClaimsFactory { get; set; }
internal IdentityOptions IdentityOptions { get; set; }
internal JwtBearerIdentityOptions JwtIdentityOptions { get; set; }
internal JwtBearerOptions JwtBearerOptions { get; set; }
public async Task<ClaimsPrincipal> CreateUserPrincipalAsync(User user) => await ClaimsFactory.CreateAsync(user);
public Task<bool> ValidateSecurityStampAsync(User user, ClaimsPrincipal principal)
{
if(user != null && UserManager.SupportsUserSecurityStamp)
{
var securityStamp = principal.FindFirstValue(IdentityOptions.ClaimsIdentity.SecurityStampClaimType);
if(securityStamp == user.SecurityStamp)
{
return Task.FromResult(true);
}
}
return Task.FromResult(false);
}
public async Task<JwtBearerSignInResult> PasswordSignInAsync(User user, string password, Device device = null)
{
if(user == null)
{
throw new ArgumentNullException(nameof(user));
}
if(await UserManager.CheckPasswordAsync(user, password))
{
var result = await SignInOrTwoFactorAsync(user);
if(result.Succeeded && device != null)
{
var existingDevice = await _deviceRepository.GetByIdentifierAsync(device.Identifier, user.Id);
if(existingDevice == null)
{
device.UserId = user.Id;
await _deviceService.SaveAsync(device);
}
}
return result;
}
return JwtBearerSignInResult.Failed;
}
public async Task<JwtBearerSignInResult> PasswordSignInAsync(string userName, string password, Device device = null)
{
var user = await UserManager.FindByNameAsync(userName);
if(user == null)
{
return JwtBearerSignInResult.Failed;
}
return await PasswordSignInAsync(user, password, device);
}
public async Task<JwtBearerSignInResult> TwoFactorSignInAsync(User user, string provider, string code, Device device = null)
{
if(user == null)
{
return JwtBearerSignInResult.Failed;
}
if(await UserManager.VerifyTwoFactorTokenAsync(user, provider, code))
{
var token = await SignInAsync(user, false);
var success = JwtBearerSignInResult.Success;
success.Token = token;
success.User = user;
if(device != null)
{
var existingDevice = await _deviceRepository.GetByIdentifierAsync(device.Identifier, user.Id);
if(existingDevice == null)
{
device.UserId = user.Id;
await _deviceService.SaveAsync(device);
}
}
return success;
}
return JwtBearerSignInResult.Failed;
}
private async Task<string> SignInAsync(User user, bool twoFactor)
{
var handler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler();
DateTime? tokenExpiration = null;
var userPrincipal = await CreateUserPrincipalAsync(user);
if(twoFactor)
{
userPrincipal.Identities.First().AddClaim(new Claim(ClaimTypes.AuthenticationMethod, JwtIdentityOptions.TwoFactorAuthenticationMethod));
if(JwtIdentityOptions.TwoFactorTokenLifetime.HasValue)
{
tokenExpiration = DateTime.UtcNow.Add(JwtIdentityOptions.TwoFactorTokenLifetime.Value);
}
}
else
{
userPrincipal.Identities.First().AddClaim(new Claim(ClaimTypes.AuthenticationMethod, JwtIdentityOptions.AuthenticationMethod));
if(JwtIdentityOptions.TokenLifetime.HasValue)
{
tokenExpiration = DateTime.UtcNow.Add(JwtIdentityOptions.TokenLifetime.Value);
}
}
var descriptor = new SecurityTokenDescriptor
{
Issuer = JwtIdentityOptions.Issuer,
SigningCredentials = JwtIdentityOptions.SigningCredentials,
Audience = JwtIdentityOptions.Audience,
Subject = userPrincipal.Identities.First(),
Expires = tokenExpiration
};
var securityToken = handler.CreateToken(descriptor);
return handler.WriteToken(securityToken);
}
private async Task<JwtBearerSignInResult> SignInOrTwoFactorAsync(User user)
{
if(UserManager.SupportsUserTwoFactor &&
await UserManager.GetTwoFactorEnabledAsync(user) &&
(await UserManager.GetValidTwoFactorProvidersAsync(user)).Count > 0)
{
var twoFactorToken = await SignInAsync(user, true);
var twoFactorResult = JwtBearerSignInResult.TwoFactorRequired;
twoFactorResult.Token = twoFactorToken;
twoFactorResult.User = user;
return twoFactorResult;
}
var token = await SignInAsync(user, false);
var result = JwtBearerSignInResult.Success;
result.Token = token;
result.User = user;
return result;
}
}
}

View File

@@ -1,34 +0,0 @@
using Bit.Core.Models.Table;
namespace Bit.Core.Identity
{
public class JwtBearerSignInResult
{
private static readonly JwtBearerSignInResult _success = new JwtBearerSignInResult { Succeeded = true };
private static readonly JwtBearerSignInResult _failed = new JwtBearerSignInResult();
private static readonly JwtBearerSignInResult _lockedOut = new JwtBearerSignInResult { IsLockedOut = true };
private static readonly JwtBearerSignInResult _notAllowed = new JwtBearerSignInResult { IsNotAllowed = true };
private static readonly JwtBearerSignInResult _twoFactorRequired = new JwtBearerSignInResult { RequiresTwoFactor = true };
public bool Succeeded { get; protected set; }
public bool IsLockedOut { get; protected set; }
public bool IsNotAllowed { get; protected set; }
public bool RequiresTwoFactor { get; protected set; }
public string Token { get; set; }
public User User { get; set; }
public static JwtBearerSignInResult Success => _success;
public static JwtBearerSignInResult Failed => _failed;
public static JwtBearerSignInResult LockedOut => _lockedOut;
public static JwtBearerSignInResult NotAllowed => _notAllowed;
public static JwtBearerSignInResult TwoFactorRequired => _twoFactorRequired;
public override string ToString()
{
return IsLockedOut ? "Lockedout" :
IsNotAllowed ? "NotAllowed" :
RequiresTwoFactor ? "RequiresTwoFactor" :
Succeeded ? "Succeeded" : "Failed";
}
}
}