Hello
Dart client:
...
Future<LoggedUser> authenticate(
String baseUrl,
String username,
String password,
) async {
final clientOptions = ClientOptions(baseUrl: baseUrl);
_client = ClientFactory.createWith(clientOptions);
final req = Authenticate(
provider: "credentials",
userName: username,
password: password,
useTokenCookie: true,
);
final res = await client.send(req);
return LoggedUser(displayName: res.displayName ?? "");
}
...
Auth feature configuration:
...
appHost.Plugins.Add(new AuthFeature(() =>
new WcsUserSession(),
new IAuthProvider[]
{
new JwtAuthProvider(appHost.AppSettings)
{
RequireSecureConnection = false,
UseTokenCookie = true,
AuthKeyBase64 = appHost.AppSettings.GetString("AuthKeyBase64"),
ExpireTokensIn = TimeSpan.FromMinutes(1),
ExpireRefreshTokensIn = TimeSpan.FromDays(365 * 50),
CreatePayloadFilter = (payload,session) =>
{
payload["external_id"] = ((WcsUserSession)session).ExternalId?.ToString();
},
PopulateSessionFilter = (session, payload, _) =>
{
if (session is not WcsUserSession userSession) return;
userSession.ExternalId = payload["external_id"];
}
},
new WcsCredentialsAuthProvider(),
new WcsBasicAuthProvider()
})
{
IncludeDefaultLogin = false,
IncludeAssignRoleServices = false,
IncludeRegistrationService = false
});
});
...
WcsCredentialsAuthProvider
...
public class WcsCredentialsAuthProvider : CredentialsAuthProvider, IUserSessionSourceAsync
{
public override async Task<bool> TryAuthenticateAsync(IServiceBase authService, string userName, string password, CancellationToken ct = new())
{
using var db = await authService.TryResolve<IDbConnectionFactory>().OpenAsync(ct);
try
{
var user = await db.SingleAsync<User>(x => x.Username == userName, token: ct);
if (user == null || !authService.TryResolve<IPasswordHasher>().VerifyPassword(user.Password, password, out _))
throw HttpError.Unauthorized(Msgs.Get(Qualifier.Unauthorized));
if (!user.Active ?? true)
throw HttpError.Forbidden(Msgs.Get(Qualifier.Forbidden));
var session = (WcsUserSession)await authService.GetSessionAsync(token: ct);
session.DisplayName = user.DisplayName;
session.UserAuthId = user.Id.ToString();
session.UserAuthName = user.Username;
session.IsAuthenticated = true;
session.Roles = [user.Role.ToString() ?? throw new ArgumentException(Msgs.Get(Qualifier.FatalError))];
session.AuthProvider = "credentials";
session.UserName = user.Username;
session.IpAddress = authService.Request.RemoteIp;
session.ExternalId = user.ExternalId;
await db.LogEventAsync(LogEventType.Login, $"{userName} (IP: {authService.Request.RemoteIp ?? "N/A"})", ct);
return true;
}
catch (Exception e)
{
await db.LogEventAsync(LogEventType.LoginFailed, $"{userName} | {password} | {e.Message} (IP: {authService.Request.RemoteIp ?? "N/A"})", ct);
throw;
}
}
public async Task<IAuthSession> GetUserSessionAsync(string userAuthId, CancellationToken ct = new())
{
using var db = await HostContext.TryResolve<IDbConnectionFactory>().OpenAsync(ct);
var user = await db.SingleByIdAsync<User>(userAuthId.ToLong(-1), token: ct);
if (user == null)
throw HttpError.Unauthorized(Msgs.Get(Qualifier.Unauthorized));
if (!(user.Active ?? true))
throw HttpError.Forbidden(Msgs.Get(Qualifier.Forbidden));
var session = new WcsUserSession
{
DisplayName = user.DisplayName,
UserAuthId = user.Id.ToString(),
UserAuthName = user.Username,
IsAuthenticated = true,
Roles = [user.Role.ToString() ?? throw new ArgumentException(Msgs.Get(Qualifier.FatalError))],
AuthProvider = "credentials",
UserName = user.Username,
ExternalId = user.ExternalId
};
return session;
}
}
...
Client authenticates successfully.
After one minute client token has expired and it triggers GetUserSessionAsync, but after initial token expiration GetUserSessionAsync is triggered on every request.
Message logged on server is:
JWT BearerToken 'eyJ0...' failed: Token has expired, trying Refresh Token...
I guess that bearer token is not refreshed on client side after reauthentication (GetUserSessionAsync).
I also have spa app that uses same api via javascript and this behavior is not observed.
Am I missing something?
Kind regards