Our shop has been using ServiceStack libraries for several years now without many issues. Recently, after upgrading from the 5.12 version to the 6.0 version, we are seeing a credential-based authentication request returning no BearerToken.
I have read the release notes section on JWT changes, here, and I understand the "breaking" change, as well as here, where it describes how to revert back to returning the bearer token in the response. However, the suggested change is for the JwtAuthProvider, not the CredentialsAuthProvider. The CredentialsAuthProvider does not have a UseTokenCookie property.
Is there a way to revert back to returning the bearer token in the authentication response, when the auth provider is a CredentialsAuthProvider?
For what it's worth, here is the relevant server code:
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
private readonly IDbContext _dbContext;
private const string AuthProviderType = "credentials";
public CustomCredentialsAuthProvider(IDbContext dbContext)
{
_dbContext = dbContext;
}
public override async Task<bool> TryAuthenticateAsync(IServiceBase authService,
string userName, string password, CancellationToken token=default)
{
var userAuthentication = _dbContext.GetUserAuthenticationData(userName);
return (userAuthentication != null && HashHelper.VerifyHash(password, userAuthentication.PasswordSalt, userAuthentication.PasswordHash));
}
public override async Task<IHttpResult> OnAuthenticatedAsync(IServiceBase authService,
IAuthSession session, IAuthTokens tokens,
Dictionary<string, string> authInfo, CancellationToken token=default)
{
var customUserSession = (CustomUserSession)session;
var userId = _dbContext.GetUserId(string.Empty, customUserSession.UserAuthName);
if (!userId.HasValue) throw new AuthenticationException("Unable to locate UserId");
var claims = _dbContext.GetClaims(userId.Value);
var postalCode = _dbContext.GetPostalCode(userId.Value);
HydrateCustomUserSession(claims.ToList(), customUserSession, postalCode);
//Call base method to Save Session and fire Auth/Session callbacks:
return result = await base.OnAuthenticatedAsync(authService, customUserSession, tokens, authInfo, token);
}
private void HydrateCustomUserSession(IReadOnlyCollection<Claims> claims, CustomUserSession customUserSession, string postalCode)
{
customUserSession.AuthProvider = AuthProviderType;
customUserSession.CreatedAt = DateTime.UtcNow;
customUserSession.UserId = Convert.ToInt32(claims.First(item => item.ClaimName == "sub").ClaimValue);
customUserSession.UserStatusId = Convert.ToInt32(claims.First(item => item.ClaimName == "user_status_id").ClaimValue);
customUserSession.UserAuthId = claims.First(item => item.ClaimName == "sub").ClaimValue;
customUserSession.FirstName = claims.First(item => item.ClaimName == "given_name").ClaimValue;
customUserSession.LastName = claims.First(item => item.ClaimName == "family_name").ClaimValue;
customUserSession.Email = claims.First(item => item.ClaimName == "email").ClaimValue;
customUserSession.Company = claims.First(item => item.ClaimName == "company_name").ClaimValue;
customUserSession.CompanyId = Convert.ToInt32(claims.First(item => item.ClaimName == "company_id").ClaimValue);
customUserSession.CompanyTypeId = Convert.ToInt32(claims.First(item => item.ClaimName == "company_type_id").ClaimValue);
customUserSession.PostalCode = postalCode;
var productIds = claims.First(item => item.ClaimName == "product_ids").ClaimValue;
if (!string.IsNullOrEmpty(productIds))
{
customUserSession.ProductIds = productIds.Split(",").Select(int.Parse).ToList();
}
else
{
customUserSession.ProductIds = new List<int>();
}
var productFeatureIds = claims.First(item => item.ClaimName == "product_feature_ids").ClaimValue;
if (!string.IsNullOrEmpty(productFeatureIds))
{
customUserSession.ProductFeatureIds = productFeatureIds.Split(",").Select(int.Parse).ToList();
}
else
{
customUserSession.ProductFeatureIds = new List<int>();
}
var userRoles = claims.First(item => item.ClaimName == "user_roles").ClaimValue;
if (!string.IsNullOrEmpty(userRoles))
{
customUserSession.UserRoles = userRoles.Split(",").Select(x => new UserRole(x)).ToList();
}
else
{
customUserSession.UserRoles = new List<UserRole>();
}
}
}
On the client side, this is a batch-driven processor, not a web app. The authentication portion is here:
private static void GetBearerToken()
{
var authRequest = new Authenticate()
{
UserName = ConfigHelper.ServiceUserName,
Password = ConfigHelper.ServicePassword,
provider = "credentials",
RememberMe = true
};
var authResponse = _authServiceClient.Post(authRequest);
// BearerToken was returned until the auth service was updated to using ServiceStack 6.0
// After using 6.0, BearerToken is empty.
_myOtherServiceClient.BearerToken = authResponse.BearerToken;
}
Once populated, the bearer token is used to authenticate calls to other services. Thanks for any help!
CodePudding user response:
If you're using JWT, you'll have the JwtAuthProvider
configured in the AuthFeature
plugin which is what needs to be configured with UseTokenCookie=false
in order for the JWT Tokens to be populated on the Response DTO (i.e. instead of using Token Cookies), as explained in the linked docs.
new JwtAuthProvider(AppSettings) {
UseTokenCookie = false
}
Although you should consider using the recommended JWT Token cookies as successful authentication will still result in an authenticated _authServiceClient
:
var authResponse = _authServiceClient.Post(authRequest);
// manual JWT handling is unnecessary when using Token Cookies
//_myOtherServiceClient.BearerToken = authResponse.BearerToken;
Since the JWT token is returned in a HTTP Cookie, which will save requiring additional effort from manually handling/populating Bearer Tokens, including server auto-refreshing of expired JWT Bearer Tokens in v6.