How to use JWT with WebSocketChannel in Flutter - flutter

I have an existing Websocket Channel which needs authenticate user by his JWT in order to send/receive messages using this socket connection. The problem is - I don't know how to send my access token in message body when establishing connection. The official documentation says:
"If the url contains user information this will be passed as basic authentication when setting up the connection."
But in my case JWT is passed in a message like this:
{"method":"auth","accessToken":"${MY_TOKEN}"}
I tried to connect by passing JWT in headers or use sink after connection is established, but when I send a new message, it only calls onDone callback and closes connection.
final _channel = IOWebSocketChannel.connect('${WEB_SOCKET_URL}');
...
void initState() {
_channel.stream.listen((message) {
print('message');
}, onError: (error, StackTrace stackTrace) {
print('error');
}, onDone: () {
print('done');
});
_channel.sink.add({
"method": "auth",
"accessToken": "${MY_TOKEN}"
});
}
I expect connection to be established and then I can use it to send/received messages but it's only closes when I try to use "sink.add()" method.

Give a try to this method. This is easy as mentioned in https://api.flutter.dev/flutter/dart-io/WebSocket/connect.html
final WebSocketChannel channel =
IOWebSocketChannel.connect("ws://<socketurl>/ws/",headers: {
'Content-type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer $yourtoken'
});

You may pass it in the URL:
WebSocketChannel.connect("ws://localhost:8080/api/foo/ws?authorization=eyJhbGciOiJIUzI1...")
Using IOWebSocketChannel as suggested throws a platform error if used with flutter web, whereas WebSocketChannel automatically determines the proper platform.
Traditionally it was considered poor practice to have credentials in
query params because URLs can get stored in places such as logs for
proxies, browser history, etc. However, neither of those concerns
apply to websockets (a browser won't keep history of the connections
made by a page), and proxies do not have access to the URL when there
is a TLS tunnel. This concern arose when non-TLS interactions were the
default.
source

You are trying to send an object on the socket I think it is not happy with that. The server may not be able trop properly handle the error of receiving something other than a json string and closes the connection.
Try this to send a json string:
var message = {
"method": "auth",
"accessToken": "${MY_TOKEN}"
};
_channel.sink.add(jsonEncode(message));

Related

How to read client post request body form dart server sent by a client

I have a dart server
httpServer = await HttpServer.bind(InternetAddress.anyIPv4, myPort);
and the client should send a post request with some json data in it's body,
how to read that body
..addRouter(fileAddedToShareSpaceEndPoint, HttpMethod.POST,
(request, response) {
//? here make a provider to handle peer share space items then update the items if the viewed items are for the updated peer
var headers = request.headers;
print(headers);
})
I got some answers saying that you can use the following code but I'am getting an error with both
var body1 = await request.transform(Utf8Decoder()).join();
// or
var body2 = request.transform(utf8.decoder).join();
and I am already have a full server setup and it's running so I don't wanna use other library like shelf or other thing because it's gonna be a lot of migration work

Spotify API demanding a parameter that is not required

I know this is a particular issue that I'm having but I'm struggling with it for ages...If anyone could give me a hint on how to proceed It would be great.
I'm trying to integrate my flutter app to Spotify API and according to Spotify docs, after the user accepted the conditions at their site, it would be redirected back with a code - In my application I'm already retrieving this code. After that, I need to make a request for another endpoint to "trade" my code for an auth.
The docs demand a POST with 3 parameters(grant_type,redirect_uri and code). But even though I send all of this informations I get a (400 -Bad Request) with the description:
{"error":"invalid_request","error_description":"code_verifier
required"}
But note that code_verifier is only required for a PKCE request, as far as I understand.
My post method is the following:
Future getUserAccessToken(String authorizationCode) async {
// Get the Access Token in exchange for the Authorization Code
var url = 'https://accounts.spotify.com/api/token';
var body = {
'grant_type': 'authorization_code',
'code': authorizationCode,
'redirect_uri': redirectUrl,
};
var response = await post(Uri.parse(url), body: body, headers: {
'Content-Type': "application/x-www-form-urlencoded",
'Authorization':
'Basic ${base64Encode(utf8.encode('$clientID:$clientSecret'))}',
});
}
They also demand this at the authorization header:
Base 64 encoded string that contains the client ID and client secret key. The field must have the format: Authorization: Basic
I would recommend going forward with PKCE. It will protect your app against CSRF and authorization code injection attacks. It's also not too bad to implement.
All you should have to do is:
Generate a random string using the characters A-Z, a-z, 0-9, and the punctuation characters -._~ (hyphen, period, underscore, and tilde), between 43 and 128 characters long. This is your code_verifier
Hash that string with SHA256. This is your code_challenge.
Send those values in the requests as the documentation indicates, which means base64 url encoding them, as well. The Spotify doc says "The body of this POST request must contain the following parameters encoded in application/x-www-form-urlencoded".
Also, in case you haven't seen it, there's a OAuth flow example implemented in JS here: https://github.com/spotify/web-api-auth-examples/blob/master/authorization_code/app.js Their copy of this portion looks like:
var authOptions = {
url: 'https://accounts.spotify.com/api/token',
form: {
code: code,
redirect_uri: redirect_uri,
grant_type: 'authorization_code'
},
headers: {
'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64'))
},
json: true
};
request.post(authOptions, function(error, response, body) {...})
The example has "request": "~2.83.0" in the package.json.
Notice how they use form which I would assume is base 64 url encoding the body, as the docs indicate. Not sure how the post method you're using with dart/flutter works with respect to base 64 url encoding, so that's something to investigate, as well.
Credit to https://www.oauth.com/oauth2-servers/pkce/authorization-request/ for providing some of the text in this response

how can I make Flutter graphql subscription work with ferry package?

I have a graphql api with apollo-server. I tested all queries, mutations and subscriptions with Graphql Playground.
I am developing the client app in Flutter using Ferry package as grapqhl client. All queries and mutations work fine, but subscriptions don't.
When sending a subscription request the websocket connection is established, however the subscription is not started. I tested the subscription on the Graphql Playground and the connection request messages looks like this
Graphql Playground network panel
but with ferry client it get stuck on connection_init
Flutter Web app network panel
var link = WebSocketLink(
"ws://localhost:4000/graphql",
initialPayload: {"subscriptionParam": arg},
);
var client = Client(link: link);
client.request(request).listen((data) {//request is an object from autogenerated class from ferry
log(data.toString());//never gets here
}, onError: (error, stack) {
log("Subscription error: " + error.toString());
});
What is wrong in my code? Help please!
So guys I solved my problem, the issue was related to link not sending Sec-WebSocket-Protocol:graphql-ws on connection request headers. So I change the link initialization to:
final link = WebSocketLink(
null, //Global.graphqlWsServerUrl,
autoReconnect: true,
reconnectInterval: Duration(seconds: 1),
initialPayload: {"subscriptionParam": arg},
channelGenerator: () => WebSocketChannel.connect(Uri.parse(Global.graphqlWsServerUrl), protocols: ['graphql-ws']),
);

Response not submitted when rxEnd is used in HTTP server

I have a two verticle server written in vert.x + reactive extensions. HTTP server verticle uses event bus to send requests to the DB verticle. After receiving the response from the DB verticle (through event bus) I send the response to the http client using rxEnd. However clients does not seem to receive this response and times out eventually. If I were to use end() instead, things work fine. I use postman to test this REST API. Please see below for the code which forward results from the DB verticle to client.
routerFactory.addHandlerByOperationId("createChargePoints", routingContext -> {
RequestParameters params = routingContext.get("parsedParameters");
RequestParameter body = params.body();
JsonObject jsonBody = body.getJsonObject();
vertx.eventBus().rxRequest("dbin", jsonBody)
.map(message -> {
System.out.println(message.body());
return routingContext.response().setStatusCode(200).rxEnd(message.body().toString());
})
.subscribe(res -> {
System.out.println(res);
}, res -> {
System.out.println(res);
});
});
The rxEnd method is a variant of end that returns a Completable. The former is lazy, the latter is not.
In other words, if you invoke rxEnd you have to subscribe to the Completable otherwise nothing happens.
Looking at the code of your snippet, I don't believe using rxEnd is necessary. Indeed, it doesn't seem like you need to know if the request was sent succesfully.

Atlassian Jira - 401 only when using query parameters

Currently working on a JIRA addon using the ACE framework. Executing a request using the integrated httpClient.
When I make a request such as this
https://instance.atlassian.net/rest/api/3/search
it works fine using the header Authorization: JWT <token> but when I run the same request with a query parameter like this
https://instance.atlassian.net/rest/api/3/search?maxResults=1
the request fails with a 401. I have confirmed that the JWT is not expired due to reverting the query parameters and seeing success again.
My atlassian-connect.json has scope READ as requested by the endpoint.
Any suggestions?
I was surprised that the rest call "rest/api/2/search?maxResults=1" worked. But it did when I was logged into my instance.
If I try that as JQL in Issue Search (maxResults=1), I get an invalid or unauthorized error message.
My instance is on premise (API V2). Yours appears to be in the cloud (V3). So it may be that the REST search works more like the Issue Search in V3 and is therefore returning the 401
It's a guess that should be easy to test... replace your maxResults=1 with some actual JQL or a filter ID and see if your results change
Since you're using ACE and utilizing httpClient, you might want to try the checkValidToken() route instead. The snippet below worked for me.
app.get('/mySearch', addon.checkValidToken(), function(req, res) {
var httpClient = addon.httpClient(req);
httpClient.get({
url: '/rest/api/3/search?maxResults=1',
headers: {
'X-Atlassian-Token': 'nocheck',
'Content-Type': 'application/json'
}
},
function (err, httpResponse, body) {
if (err) {
return console.error('Search failed:', err);
}
console.log('Search successful:', body);
});
});