I have a Vert.x REST service that receive requests with jwt tokens, and I want to call my another REST service passing received token. Between router handler and WebClient call I have a business logic layer. My question is if there is a method to provide token to webClient other than passing it explicitly through my business logic layer? In other words is it possible to retrieve somehow my RoutingContext and token from e.g. vertxContext or an other component?
Example code demonstrating what I would like to achieve:
Verticle cass
public class RestApiVerticle extends AbstractVerticle {
businessLogicService service;
#Override
public void start() throws Exception {
initService();
HttpServer server = vertx.createHttpServer();
Router router = Router.router(vertx);
JWTAuth authProvider = JWTAuth.create(vertx, getAuthConfig());
router.route("/*").handler(JWTAuthHandler.create(authProvider));
router.route("/somePath").handler(this::handleRequest);
server.requestHandler(router::accept).listen(config().getInteger("port"));
}
private void handleRequest(RoutingContext context){
service.doSomeBusinessLogic(); //I could pass context here, but I thing this is not a proper way to do it, as business logic should not know about RequestContext
}
private void initService(){
ExternalAPICaller caller = new ExternalAPICaller(WebClient.create(vertx));
service = new BusinessLogicService(caller);
}
private JsonObject getAuthConfig() {
return new JsonObject();
}
}
BusinessLogicService:
public class BusinessLogicService {
ExternalAPICaller caller;
public BusinessLogicService(ExternalAPICaller caller){
this.caller = caller;
}
public void doSomeBusinessLogic(){
caller.doSth();
}
}
ExternalAPICaller:
public class ExternalAPICaller {
WebClient client;
public ExternalAPICaller(WebClient client){
this.client = client;
}
public void doSth(){
String TOKEN = null; // I would like to retrive here my token from some vertx component
client.post("externalAPIpath")
.putHeader("Authorization", "Bearer" + TOKEN)
.send(ctx -> {
//(..)
});
}
}
My implementation is in JavaScript (Node.js/Express), but I used cookies to send the JWT to the client.
res.cookie("auth", token);
return res.redirect(`http://localhost:3000/socialauthredirect`);
When you call your do business logic method you could pass the request authorization header value as it contains your untouched jwt token. Then on your web client add a header with that value and of course named authorization and your token is forwarded to the next service.
Related
For authentication currently we are using JWT, so once a token is created it's created for a lifetime, and if we set a time expire, the token will expire.
Is there any way to expire token?
While clicking log out button, I need to destroy the token.
I'm using ASP.NET Core WebAPI.
I think cancelling JWT is the best way to handle logout. Piotr explained well in his blog: Cancel JWT tokens
We will start with the interface:
public interface ITokenManager
{
Task<bool> IsCurrentActiveToken();
Task DeactivateCurrentAsync();
Task<bool> IsActiveAsync(string token);
Task DeactivateAsync(string token);
}
And process with its implementation, where the basic idea is to keep
track of deactivated tokens only and remove them from a cache when not
needed anymore (meaning when the expiry time passed) – they will be no
longer valid anyway.
public class TokenManager : ITokenManager
{
private readonly IDistributedCache _cache;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IOptions<JwtOptions> _jwtOptions;
public TokenManager(IDistributedCache cache,
IHttpContextAccessor httpContextAccessor,
IOptions<JwtOptions> jwtOptions
)
{
_cache = cache;
_httpContextAccessor = httpContextAccessor;
_jwtOptions = jwtOptions;
}
public async Task<bool> IsCurrentActiveToken()
=> await IsActiveAsync(GetCurrentAsync());
public async Task DeactivateCurrentAsync()
=> await DeactivateAsync(GetCurrentAsync());
public async Task<bool> IsActiveAsync(string token)
=> await _cache.GetStringAsync(GetKey(token)) == null;
public async Task DeactivateAsync(string token)
=> await _cache.SetStringAsync(GetKey(token),
" ", new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromMinutes(_jwtOptions.Value.ExpiryMinutes)
});
private string GetCurrentAsync()
{
var authorizationHeader = _httpContextAccessor
.HttpContext.Request.Headers["authorization"];
return authorizationHeader == StringValues.Empty
? string.Empty
: authorizationHeader.Single().Split(" ").Last();
}
private static string GetKey(string token)
=> $"tokens:{token}:deactivated";
}
As you can see, there are 2 helper methods that will use the current
HttpContext in order to make things even easier.
Next, let’s create a middleware that will check if the token was
deactivated or not. That’s the reason why we should keep them in cache
– hitting the database with every request instead would probably kill
your app sooner or later (or at least make it really, really slow):
public class TokenManagerMiddleware : IMiddleware
{
private readonly ITokenManager _tokenManager;
public TokenManagerMiddleware(ITokenManager tokenManager)
{
_tokenManager = tokenManager;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
if (await _tokenManager.IsCurrentActiveToken())
{
await next(context);
return;
}
context.Response.StatusCode = (int) HttpStatusCode.Unauthorized;
}
}
Eventually, let’s finish our journey with implementing an endpoint for
canceling the tokens:
[HttpPost("tokens/cancel")]
public async Task<IActionResult> CancelAccessToken()
{
await _tokenManager.DeactivateCurrentAsync();
return NoContent();
}
For sure, we could make it more sophisticated, via passing the token
via URL, or by canceling all of the existing user tokens at once
(which would require an additional implementation to keep track of
them), yet this is a basic sample that just works.
Make sure that you will register the required dependencies in your
container and configure the middleware:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddTransient<TokenManagerMiddleware>();
services.AddTransient<ITokenManager, Services.TokenManager>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddDistributedRedisCache(r => { r.Configuration = Configuration["redis:connectionString"];
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
...
app.UseAuthentication();
app.UseMiddleware<TokenManagerMiddleware>();
app.UseMvc();
}
And provide a configuration for Redis in appsettings.json file:
"redis": {
"connectionString": "localhost"
}
Try to run the application now and invoke the token cancellation[sic]
endpoint – that’s it.
Actually the best way to logout is just remove token from the client. And you can make lifetime of tokens short (5-15 minutes) and implement refresh tokens for additions security. In this case there are less chance for attacker to do something with your JWT
If you have implemented the login scenario with the refresh token, You can remove the refresh token from the server and then , and then you should remove the token from the client.
In my Micronaut app I have a simple REST controller:
public class Response {
private String code;
public Response(String code) {
this.code = code;
}
}
#Controller("/api/test")
public class TestController {
#Post("/")
public Response index() {
return new Response("OK");
}
}
How can I tests this edpoint? I tried using
#MicronautTest
public class TestControllerTest {
#Inject
EmbeddedServer server;
#Inject
#Client("/")
HttpClient client;
#Test
void testResponse() {
String response = client.toBlocking()
.retrieve(HttpRequest.POST("/api/test/")); // FIXME `HttpRequest.POST` requires body
assertEquals("{\"code\": \"OK\"}", response);
}
but HttpRequest.POST requires an additional body argument to be specified. In my case there is no body to be sent. (In the real code it is a request to initialize a new object and thus it has to be POST).
Usually, when you implement a POST action, you expect that there is a body sent with the request. In your example, you don't accept any POST body, but you still need to pass anything in the unit test.
You can instantiate the HttpRequest object in the following way:
HttpRequest.POST("/api/test/", "");
You can't pass null, it has to be some non-null value (like an empty string.)
there are numerous posts explaining how to add dynamic header into request using Feign Interceptor
#Bean
public RequestInterceptor requestInterceptor() {
#Override
public void apply(RequestTemplate requestTemplate) {
var jwtToken = refreshAccessTokenClient.refresh();
requestTemplate.header("Authorization", jwtToken);
}
}
is there any possibility to share jwtToken across many threads and refresh in periodically? Current solution is sub-optimal and makes 1 extra call each time.
I came up with solution:
#Bean
public RequestInterceptor requestInterceptor(#Value("${service.login}") String login,
#Value("${service.apiKey}") String apiKey) {
return
requestTemplate -> {
var request = LoginRequest.builder().loginId(login).apiKey(apiKey).build();
var jwtToken = authAuthProvider.login(request).getData().getAccessToken();
requestTemplate.header("Authorization", "Bearer " + jwtToken);
};
}
I blindly request jwtToken for every API call. It doesn't look like effective solution. Should look for some #Cacheable thing with TLL?
My (Cucumber) BDD unit test using WebTestClient is failing (with a 403 Forbidden), when I believe it should be passing. After some debugging, I established that this is because the CSRF check is failing, which suggests the mutateWith(csrf()) operation is not working. What am I doing wrong?
My test scenario:
Scenario Outline: Login
Given that player "<player>" exists with password "<password>"
And presenting a valid CSRF token
When log in as "<player>" using password "<password>"
Then program accepts the login
My test steps code (note the presence of client.mutateWith(csrf())):
#SpringBootTest(...)
#AutoConfigureWebTestClient
public class WebSteps {
#Autowired
private WebTestClient client;
...
private WebTestClient.ResponseSpec response;
#Given("presenting a valid CSRF token")
public void presenting_a_valid_CSRF_token() {
client.mutateWith(csrf());
}
#When("log in as {string} using password {string}")
public void log_in_as_using_password(final String player,
final String password) {
response = client.post().uri("/login")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(BodyInserters.fromFormData("username", player)
.with("password", password))
.exchange();
}
#Then("program accepts the login")
public void program_accepts_the_login() {
response.expectStatus().isFound().expectHeader().valueEquals("Location",
"/");
}
...
Despite its name, the mutateWith() method does not really mutate its object. Rather, it returns a new object that has had the mutation applied. Therefore instead of writing
#Given("presenting a valid CSRF token")
public void presenting_a_valid_CSRF_token() {
client.mutateWith(csrf());
}
write
#Given("presenting a valid CSRF token")
public void presenting_a_valid_CSRF_token() {
client = client.mutateWith(csrf());
}
This error is more likely to occur in a Cucumber test because of the way that test steps alter shared state (The client object), rather than use a fluent API with a long chain of calls.
Is it possible to resend a RequestFactory transmission? I'd like to do the equivalent of this: How to resend a GWT RPC request when using RequestFactory. It is fairly simple to resend the same payload from a previous request, but I also need to place a call to the same method. Here's my RequestTransport class, and I am hoping to just "refire" the original request after taking care of, in this case, a request to the user for login credentials:
package org.greatlogic.rfexample2.client;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.Response;
import com.google.web.bindery.requestfactory.gwt.client.DefaultRequestTransport;
/**
* Every request factory transmission will pass through the single instance of this class. This can
* be used to ensure that when a response is received any global conditions (e.g., the user is no
* longer logged in) can be handled in a consistent manner.
*/
public class RFERequestTransport extends DefaultRequestTransport {
//--------------------------------------------------------------------------------------------------
private IClientFactory _clientFactory;
//==================================================================================================
private final class RFERequestCallback implements RequestCallback {
private RequestCallback _requestCallback;
private RFERequestCallback(final RequestCallback requestCallback) {
_requestCallback = requestCallback;
} // RFERequestCallback()
#Override
public void onError(final Request request, final Throwable exception) {
_requestCallback.onError(request, exception);
} // onError()
#Override
public void onResponseReceived(final Request request, final Response response) {
if (response.getStatusCode() == Response.SC_UNAUTHORIZED) {
_clientFactory.login();
}
else {
_clientFactory.setLastPayload(null);
_clientFactory.setLastReceiver(null);
_requestCallback.onResponseReceived(request, response);
}
} // onResponseReceived()
} // class RFERequestCallback
//==================================================================================================
#Override
protected void configureRequestBuilder(final RequestBuilder builder) {
super.configureRequestBuilder(builder);
} // configureRequestBuilder()
//--------------------------------------------------------------------------------------------------
#Override
protected RequestCallback createRequestCallback(final TransportReceiver receiver) {
return new RFERequestCallback(super.createRequestCallback(receiver));
} // createRequestCallback()
//--------------------------------------------------------------------------------------------------
void initialize(final IClientFactory clientFactory) {
_clientFactory = clientFactory;
} // initialize()
//--------------------------------------------------------------------------------------------------
#Override
public void send(final String payload, final TransportReceiver receiver) {
String actualPayload = _clientFactory.getLastPayload();
TransportReceiver actualReceiver;
if (actualPayload == null) {
actualPayload = payload;
actualReceiver = receiver;
_clientFactory.setLastPayload(payload);
_clientFactory.setLastReceiver(receiver);
}
else {
actualReceiver = _clientFactory.getLastReceiver();
}
super.send(actualPayload, actualReceiver);
} // send()
//--------------------------------------------------------------------------------------------------
}
Based upon Thomas' suggestion I tried sending another request, and just replaced the payload and receiver in the RequestTransport.send() method, and this worked; I guess there is no further context retained by request factory, and that the response from the server is sufficient for RF to determine what needs to be done to unpack the response beyond the request and response that are returned to the RequestCallback.onResponseReceived() method. If anyone is interested in seeing my code then just let me know and I'll post it here.
It's possible, but you have a lot to do.
I had the same idea. And i was searching for a good solution for about 2 days. I tried to intercept the server call on RequestContext.java and on other classes. But if you do that you have to make your own implementation for nearly every class of gwt requestfactories. So i decided to go a much simpler approach.
Everywhere where I fired a Request, i handled the response and fired it again.
Of course you have to take care, that you don't get in to a loop.