mirror of
https://github.com/bitwarden/server.git
synced 2025-02-01 23:31:41 +01:00
remove deprecated jwt bearer authentication method
This commit is contained in:
parent
811bbbfe0a
commit
d8c0994ed3
@ -1,11 +1,7 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Bit.Core.Identity;
|
||||
using Bit.Core.Models.Api;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.Api.Controllers
|
||||
{
|
||||
@ -13,54 +9,12 @@ namespace Bit.Api.Controllers
|
||||
[Route("auth")]
|
||||
public class AuthController : Controller
|
||||
{
|
||||
private readonly JwtBearerSignInManager _signInManager;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
public AuthController(
|
||||
JwtBearerSignInManager signInManager,
|
||||
IUserService userService)
|
||||
{
|
||||
_signInManager = signInManager;
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
[HttpPost("token")]
|
||||
[AllowAnonymous]
|
||||
public async Task<AuthTokenResponseModel> PostToken([FromBody]AuthTokenRequestModel model)
|
||||
public IActionResult PostToken()
|
||||
{
|
||||
var result = await _signInManager.PasswordSignInAsync(model.Email.ToLower(), model.MasterPasswordHash,
|
||||
model.Device?.ToDevice());
|
||||
if(result == JwtBearerSignInResult.Success)
|
||||
{
|
||||
return new AuthTokenResponseModel(result.Token, result.User);
|
||||
}
|
||||
else if(result == JwtBearerSignInResult.TwoFactorRequired)
|
||||
{
|
||||
return new AuthTokenResponseModel(result.Token, null);
|
||||
}
|
||||
|
||||
await Task.Delay(2000);
|
||||
throw new BadRequestException("Username or password is incorrect. Try again.");
|
||||
}
|
||||
|
||||
[HttpPost("token/two-factor")]
|
||||
[Authorize("TwoFactor")]
|
||||
public async Task<AuthTokenResponseModel> PostTokenTwoFactor([FromBody]AuthTokenTwoFactorRequestModel model)
|
||||
{
|
||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
||||
if(user == null)
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
var result = await _signInManager.TwoFactorSignInAsync(user, model.Provider, model.Code, model.Device?.ToDevice());
|
||||
if(result == JwtBearerSignInResult.Success)
|
||||
{
|
||||
return new AuthTokenResponseModel(result.Token, result.User);
|
||||
}
|
||||
|
||||
await Task.Delay(2000);
|
||||
throw new BadRequestException("Code is not correct. Try again.");
|
||||
throw new BadRequestException("You are using an outdated version of bitwarden that is no longer supported. " +
|
||||
"Please update your app first and try again.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,10 @@
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Bit.Api.Utilities;
|
||||
using Bit.Core;
|
||||
@ -20,6 +18,7 @@ using Bit.Api.Middleware;
|
||||
using Serilog.Events;
|
||||
using Stripe;
|
||||
using Bit.Core.Utilities;
|
||||
using IdentityModel;
|
||||
|
||||
namespace Bit.Api
|
||||
{
|
||||
@ -73,21 +72,13 @@ namespace Bit.Api
|
||||
// Identity
|
||||
services.AddCustomIdentityServices(globalSettings);
|
||||
|
||||
var jwtIdentityOptions = provider.GetRequiredService<IOptions<JwtBearerIdentityOptions>>().Value;
|
||||
services.AddAuthorization(config =>
|
||||
{
|
||||
config.AddPolicy("Application", policy =>
|
||||
{
|
||||
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme, "Bearer2", "Bearer3");
|
||||
policy.AddAuthenticationSchemes("Bearer2", "Bearer3");
|
||||
policy.RequireAuthenticatedUser();
|
||||
policy.RequireClaim(ClaimTypes.AuthenticationMethod, jwtIdentityOptions.AuthenticationMethod);
|
||||
});
|
||||
|
||||
config.AddPolicy("TwoFactor", policy =>
|
||||
{
|
||||
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme, "Bearer2", "Bearer3");
|
||||
policy.RequireAuthenticatedUser();
|
||||
policy.RequireClaim(ClaimTypes.AuthenticationMethod, jwtIdentityOptions.TwoFactorAuthenticationMethod);
|
||||
policy.RequireClaim(JwtClaimTypes.AuthenticationMethod, "Application");
|
||||
});
|
||||
});
|
||||
|
||||
@ -166,9 +157,6 @@ namespace Bit.Api
|
||||
app.UseIdentityServerAuthentication(
|
||||
GetIdentityOptions(env, IdentityServerAuthority(env, "api", "4000"), "2"));
|
||||
|
||||
// Add Jwt authentication to the request pipeline.
|
||||
app.UseJwtBearerIdentity();
|
||||
|
||||
// Add current context
|
||||
app.UseMiddleware<CurrentContextMiddleware>();
|
||||
|
||||
@ -208,7 +196,7 @@ namespace Bit.Api
|
||||
else
|
||||
{
|
||||
return $"http://localhost:{port}";
|
||||
//return $"http://192.168.1.8:{port}"; // Desktop external
|
||||
//return $"http://192.168.1.6:{port}"; // Desktop external
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="IdentityServer4" Version="1.3.1" />
|
||||
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="1.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection.AzureStorage" Version="1.0.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="1.1.2" />
|
||||
|
@ -15,7 +15,7 @@ namespace Bit.Core.Identity
|
||||
&& user.TwoFactorProvider.HasValue
|
||||
&& user.TwoFactorProvider.Value == TwoFactorProviderType.Authenticator
|
||||
&& !string.IsNullOrWhiteSpace(user.AuthenticatorKey);
|
||||
|
||||
|
||||
return Task.FromResult(canGenerate);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using IdentityServer4.Models;
|
||||
using IdentityModel;
|
||||
using IdentityServer4.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
|
||||
@ -11,13 +12,9 @@ namespace Bit.Core.IdentityServer
|
||||
return new List<ApiResource>
|
||||
{
|
||||
new ApiResource("api", new string[] {
|
||||
ClaimTypes.AuthenticationMethod,
|
||||
ClaimTypes.NameIdentifier,
|
||||
ClaimTypes.Email,
|
||||
"securitystamp",
|
||||
|
||||
"name",
|
||||
"email",
|
||||
JwtClaimTypes.Name,
|
||||
JwtClaimTypes.Email,
|
||||
JwtClaimTypes.EmailVerified,
|
||||
"sstamp", // security stamp
|
||||
"plan",
|
||||
"device",
|
||||
|
@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Builder;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using IdentityModel;
|
||||
|
||||
namespace Bit.Core.IdentityServer
|
||||
{
|
||||
@ -42,43 +43,40 @@ namespace Bit.Core.IdentityServer
|
||||
newClaims.AddRange(new List<Claim>
|
||||
{
|
||||
new Claim("plan", "0"), // free plan hard coded for now
|
||||
new Claim("sstamp", user.SecurityStamp),
|
||||
new Claim("email", user.Email),
|
||||
|
||||
// Deprecated claims for backwards compatability
|
||||
new Claim(_identityOptions.ClaimsIdentity.UserNameClaimType, user.Email),
|
||||
new Claim(JwtClaimTypes.Email, user.Email),
|
||||
new Claim(JwtClaimTypes.EmailVerified, user.EmailVerified ? "true" : "false"),
|
||||
new Claim(_identityOptions.ClaimsIdentity.SecurityStampClaimType, user.SecurityStamp)
|
||||
});
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(user.Name))
|
||||
{
|
||||
newClaims.Add(new Claim("name", user.Name));
|
||||
newClaims.Add(new Claim(JwtClaimTypes.Name, user.Name));
|
||||
}
|
||||
|
||||
// Orgs that this user belongs to
|
||||
var orgs = await _organizationUserRepository.GetManyByUserAsync(user.Id);
|
||||
if(orgs.Any())
|
||||
{
|
||||
var groupedOrgs = orgs.Where(o => o.Status == Core.Enums.OrganizationUserStatusType.Confirmed)
|
||||
var groupedOrgs = orgs.Where(o => o.Status == Enums.OrganizationUserStatusType.Confirmed)
|
||||
.GroupBy(o => o.Type);
|
||||
|
||||
foreach(var group in groupedOrgs)
|
||||
{
|
||||
switch(group.Key)
|
||||
{
|
||||
case Core.Enums.OrganizationUserType.Owner:
|
||||
case Enums.OrganizationUserType.Owner:
|
||||
foreach(var org in group)
|
||||
{
|
||||
newClaims.Add(new Claim("orgowner", org.OrganizationId.ToString()));
|
||||
}
|
||||
break;
|
||||
case Core.Enums.OrganizationUserType.Admin:
|
||||
case Enums.OrganizationUserType.Admin:
|
||||
foreach(var org in group)
|
||||
{
|
||||
newClaims.Add(new Claim("orgadmin", org.OrganizationId.ToString()));
|
||||
}
|
||||
break;
|
||||
case Core.Enums.OrganizationUserType.User:
|
||||
case Enums.OrganizationUserType.User:
|
||||
foreach(var org in group)
|
||||
{
|
||||
newClaims.Add(new Claim("orguser", org.OrganizationId.ToString()));
|
||||
|
@ -1,19 +1,12 @@
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Identity;
|
||||
using Bit.Core.Repositories;
|
||||
using IdentityServer4.Models;
|
||||
using IdentityServer4.Validation;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Services;
|
||||
@ -23,57 +16,26 @@ namespace Bit.Core.IdentityServer
|
||||
public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
|
||||
{
|
||||
private UserManager<User> _userManager;
|
||||
private IdentityOptions _identityOptions;
|
||||
private JwtBearerOptions _jwtBearerOptions;
|
||||
private JwtBearerIdentityOptions _jwtBearerIdentityOptions;
|
||||
private readonly IDeviceRepository _deviceRepository;
|
||||
private readonly IDeviceService _deviceService;
|
||||
|
||||
public ResourceOwnerPasswordValidator(
|
||||
UserManager<User> userManager,
|
||||
IOptions<IdentityOptions> identityOptionsAccessor,
|
||||
IOptions<JwtBearerIdentityOptions> jwtIdentityOptionsAccessor,
|
||||
IDeviceRepository deviceRepository,
|
||||
IDeviceService deviceService)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_identityOptions = identityOptionsAccessor?.Value ?? new IdentityOptions();
|
||||
_jwtBearerIdentityOptions = jwtIdentityOptionsAccessor?.Value;
|
||||
_jwtBearerOptions = Identity.JwtBearerAppBuilderExtensions.BuildJwtBearerOptions(_jwtBearerIdentityOptions);
|
||||
_deviceRepository = deviceRepository;
|
||||
_deviceService = deviceService;
|
||||
}
|
||||
|
||||
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
|
||||
{
|
||||
var oldAuthBearer = context.Request.Raw["OldAuthBearer"]?.ToString();
|
||||
var twoFactorToken = context.Request.Raw["TwoFactorToken"]?.ToString();
|
||||
var twoFactorProvider = context.Request.Raw["TwoFactorProvider"]?.ToString();
|
||||
var twoFactorRequest = !string.IsNullOrWhiteSpace(twoFactorToken) && !string.IsNullOrWhiteSpace(twoFactorProvider);
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(oldAuthBearer) && _jwtBearerOptions != null)
|
||||
{
|
||||
// support transferring the old auth bearer token
|
||||
var ticket = ValidateOldAuthBearer(oldAuthBearer);
|
||||
if(ticket != null && ticket.Principal != null)
|
||||
{
|
||||
var idClaim = ticket.Principal.Claims
|
||||
.FirstOrDefault(c => c.Type == _identityOptions.ClaimsIdentity.UserIdClaimType);
|
||||
var securityTokenClaim = ticket.Principal.Claims
|
||||
.FirstOrDefault(c => c.Type == _identityOptions.ClaimsIdentity.SecurityStampClaimType);
|
||||
if(idClaim != null && securityTokenClaim != null)
|
||||
{
|
||||
var user = await _userManager.FindByIdAsync(idClaim.Value);
|
||||
if(user != null && user.SecurityStamp == securityTokenClaim.Value)
|
||||
{
|
||||
var device = await SaveDeviceAsync(user, context);
|
||||
BuildSuccessResult(user, context, device);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(!string.IsNullOrWhiteSpace(context.UserName))
|
||||
if(!string.IsNullOrWhiteSpace(context.UserName))
|
||||
{
|
||||
var user = await _userManager.FindByEmailAsync(context.UserName.ToLowerInvariant());
|
||||
if(user != null)
|
||||
@ -110,12 +72,7 @@ namespace Bit.Core.IdentityServer
|
||||
|
||||
private void BuildSuccessResult(User user, ResourceOwnerPasswordValidationContext context, Device device)
|
||||
{
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
// Deprecated claims for backwards compatibility
|
||||
new Claim(ClaimTypes.AuthenticationMethod, "Application"),
|
||||
new Claim(_identityOptions.ClaimsIdentity.UserIdClaimType, user.Id.ToString())
|
||||
};
|
||||
var claims = new List<Claim>();
|
||||
|
||||
if(device != null)
|
||||
{
|
||||
@ -164,34 +121,6 @@ namespace Bit.Core.IdentityServer
|
||||
}});
|
||||
}
|
||||
|
||||
private AuthenticationTicket ValidateOldAuthBearer(string token)
|
||||
{
|
||||
SecurityToken validatedToken;
|
||||
foreach(var validator in _jwtBearerOptions.SecurityTokenValidators)
|
||||
{
|
||||
if(validator.CanReadToken(token))
|
||||
{
|
||||
ClaimsPrincipal principal;
|
||||
try
|
||||
{
|
||||
principal = validator.ValidateToken(token,
|
||||
_jwtBearerOptions.TokenValidationParameters, out validatedToken);
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(),
|
||||
_jwtBearerOptions.AuthenticationScheme);
|
||||
|
||||
return ticket;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<bool> TwoFactorRequiredAsync(User user)
|
||||
{
|
||||
return _userManager.SupportsUserTwoFactor &&
|
||||
|
@ -1,15 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
public class AuthTokenRequestModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
[StringLength(50)]
|
||||
public string Email { get; set; }
|
||||
[Required]
|
||||
public string MasterPasswordHash { get; set; }
|
||||
public DeviceRequestModel Device { get; set; }
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
public class AuthTokenTwoFactorRequestModel
|
||||
{
|
||||
[Required]
|
||||
public string Code { get; set; }
|
||||
[Required]
|
||||
public string Provider { get; set; }
|
||||
public DeviceRequestModel Device { get; set; }
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ using Bit.Core.IdentityServer;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using IdentityModel;
|
||||
using IdentityServer4.Services;
|
||||
using IdentityServer4.Stores;
|
||||
using IdentityServer4.Validation;
|
||||
@ -12,11 +13,8 @@ using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.WindowsAzure.Storage;
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using SqlServerRepos = Bit.Core.Repositories.SqlServer;
|
||||
|
||||
namespace Bit.Core.Utilities
|
||||
@ -70,7 +68,7 @@ namespace Bit.Core.Utilities
|
||||
{
|
||||
services.AddTransient<ILookupNormalizer, LowerInvariantLookupNormalizer>();
|
||||
|
||||
var identityBuilder = services.AddJwtBearerIdentity(options =>
|
||||
var identityBuilder = services.AddIdentity<User, Role>(options =>
|
||||
{
|
||||
options.User = new UserOptions
|
||||
{
|
||||
@ -87,18 +85,11 @@ namespace Bit.Core.Utilities
|
||||
};
|
||||
options.ClaimsIdentity = new ClaimsIdentityOptions
|
||||
{
|
||||
SecurityStampClaimType = "securitystamp",
|
||||
UserNameClaimType = ClaimTypes.Email
|
||||
SecurityStampClaimType = "sstamp",
|
||||
UserNameClaimType = JwtClaimTypes.Email,
|
||||
UserIdClaimType = JwtClaimTypes.Subject,
|
||||
};
|
||||
options.Tokens.ChangeEmailTokenProvider = TokenOptions.DefaultEmailProvider;
|
||||
}, jwtBearerOptions =>
|
||||
{
|
||||
jwtBearerOptions.Audience = "bitwarden";
|
||||
jwtBearerOptions.Issuer = "bitwarden";
|
||||
jwtBearerOptions.TokenLifetime = TimeSpan.FromDays(10 * 365);
|
||||
jwtBearerOptions.TwoFactorTokenLifetime = TimeSpan.FromMinutes(10);
|
||||
var keyBytes = Encoding.ASCII.GetBytes(globalSettings.JwtSigningKey);
|
||||
jwtBearerOptions.SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(keyBytes), SecurityAlgorithms.HmacSha256);
|
||||
});
|
||||
|
||||
identityBuilder
|
||||
|
Loading…
Reference in New Issue
Block a user