Flutter web-app: How to extract JWT from response - flutter

Maybe this question makes no sense. But let me ask anyway.
I have a Flutter web-app (not mobile) and a Rails back-end. I'm using devise-jwt on the Rails end to authenticate and send back a JWT on sign-in.
So far, so good. I can see the JWT in the response
Problem is, when I try to extract the JWT, as follows ...
postRequest(url, form).then((response) {
print(response.headers.keys);
}) ;
... I find that Authorization is not visible in response.headers.keys.
Maybe this (not exposing Authorization) is a security feature specific to web-apps. Or, I'm missing something really small.
Hence, I'm wondering if:
accessing the JWT is even possible in a Flutter web-app?
if yes, then how?
if no, then is the only option to send back an httpOnly cookie?
can the Authorization header be accessed only in mobile apps?
Thanks in advance for your help

Ok, I found out how Authorization can be made visible (by Rails) before it can be accessed at the Flutter end.
In config/initializers/cors.rb, expose Authorization header as shown
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'http://localhost:8080'
resource '*', \
headers: :any, \
expose: ['Authorization'],\
methods: [:get, :post]
end
end
This still leaves open the question of how best to store JWT on the browser - now that it can be accessed. And it seems httpOnly cookies are the way to go

Related

Persisting user state in sveltekit

I'm trying to hook up a Strapi backend to a SvelteKit frontend, and stuck on how to persist user login state so that everything doesn't just reset on refresh, or when navigating to a new page. I've tried:
Storing the jwt and user object issued by Strapi in localStorage and initializing the Svelte store with it. Seemed like I was getting close, but a) I couldn't do export const user = writable(localStorage.user) because that code was running in the browser, and I couldn't wrap it in an if (browser) {...} because import and export can only appear at the top level. Also tried a function in hooks.js to read the contents of localStorage and update the store, but it seems that functions getting called from there run on the server, even if it's the same function that works to access localStorage on login... and plus b) from what I gather, storing jwt's in localStorage is insecure.
Storing the jwt and user object in an http only cookie. Cookies and http headers seem really confusing, and I had a hard time manipulating them to store the jwt and put it in each header. But I think what really stumped me was essentially the same SSR issue of never knowing essentially how to successfully interface between the client and server. I.e. if (browser) {...} never seemed to work, or I couldn't get it to, anyway. (Happy to provide more code details on what I tried here if needed. It's a mess, but it's saved in git.)
I know this is a thing every app that has users needs to do, so I'm sure there's a way to do it in SvelteKit. But I can't find anything online that explains it, and I can't figure it out from the official docs either.
So am I missing something easy? (Probably.) Or is there a tricky way to do this?
For a SvelteKit SPA authenticating to Strapi, here's the happy path flow I would use:
SvelteKit page /routes/login.svelte collects the username (identifier) and password and a button's on:click handler posts those values via fetch to your own /routes/auth.js:post() endpoint. Why? Because now your server's endpoint is handling the login on behalf of the user so you can set an httpOnly cookie in the response.
In your auth.js endpoint post() method, you will want to do a few things:
Use fetch to post identifier and password to Strapi authentication (http://localhost:1337/auth/local) to get the JWT (response.body.jwt)
Use fetch to get user info from Strapi (http://localhost:1337/users/me). In the get, add a header called 'Authentication' with a value of 'Bearer ' + the JWT you just received from Strapi in the previous step
Return the user info from the response from Strapi and pass it back to your client in the response.body
Set a header httpOnly cookie with the value of the JWT.
Back on the client in the last part of the login button's on:click handler, take the user info from res.json() and put it in a writeable store called user or in the SvelteKit session store...
import { session } from '$app/stores'
...
const loginClickHandler = async () => {
...
const fromEndpoint = await res.json()
session.set({ user: fromEndpoint.user })
}
At this point, you have the user information persisted client-side and the JWT as an httpOnly cookie that can't be read or modified by client-side JavaScript code. Each request you make to your own server's pages or endpoints will send the JWT cookie along.
If you want to logout, call an endpoint on your server (/auth/logout) that sets the existing jwt cookie to have an Expires based on the current date/time:
response.headers['Set-Cookie'] = `jwt=; Path=/; HttpOnly; Expires=${new Date().toUTCString()}`
You would also want to clear the user object in your store (or the session store).
The main takeaway for the above example is that your client would never directly talk to Strapi. Strapi's API would be called only by your SvelteKit server's endpoints. The httpOnly jwt cookie representing your session with Strapi would be included in every request to your server's endpoints to use/validate with Strapi's API (or delete if expired or the user logged out).
There are lots of other approaches but I prefer this one for security reasons.

storing and sending jwt httponly cookie, and csrf token with postman

I have a flask API, with jwt authentication, on a httponly cookie. I installed interceptor, added the domain(with HTTPS) to the list, and enabled the requests and cookies interception.
but still,
how do I make postman send the cookie I got from logging in to the server? usually, with a simple front-end, it just happens, so I didn't think about it.
all the methods I found in postman documentation, including specifying the value with the token, but I don't have it, since I can't access the httponly cookie. (or can I?)
must I access the cookies? can it be done automatically like simply sending requests from the front-end?
any guidance will be appreciated
After a full evening of research, I did two things to make it work -
in the login request, I added a "test" script(a post-request script in postman), with the following code:
const csrf_token = pm.response.headers.get("set-cookie");
const edited_token = csrf_token.split(/[;=]/)[1];
pm.environment.set("X-CSRF-TOKEN", edited_token);
console.log(csrf_token.split(/[;=]/)[1]);
First, I got the cookie from the response, and then used a regex to separate only the token value, and set it as an environment variable. this way, I could add it as a header later, for accessing protected URLs.
The second step was to add a pre-scrit in any request with a protected URL -
in the pre-request tab, I added the following:
pm.request.headers.add({
key: 'X-CSRF-TOKEN',
value: pm.environment.get("X-CSRF-TOKEN")
});
Which only added the same token I took earlier from the "X-CSRF-TOKEN" environment variable and set it to the header.
Mission accomplished :)
I hope it will help others who bumped into this

Azure Graph API 2.0 error in refreshing token: Provided grant is invalid or malformed (AADSTS70000)

I'm following step by step guide on Microsoft's site (https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#refresh-the-access-token), everything is working correctly but every time I try to refresh the access token, I get this error: AADSTS70000: Provided grant is invalid or malformed. error_codes: 70000.
I've created a Postman collection for testing, also downloaded the official postman collection from the Microsoft's site, everything is working correctly until the access token does not expire. When it expire, trying to refresh the token always lead to an error and I'm pretty stuck with it. I've double and triple checked correspondence between redirect_url, permission, grant, copy/paste errors, waited for the access_token to expire before trying to refresh... I've done almost 100 tests, and every time I'm stuck at the refresh part!
I start with doing the normal call to Microsoft Login API in my browser, and getting the code in query string from the browser (no problems here) (please note that client_id is URL encoded because, in my test environment, client id is an URL due to the configuration of the Drupal portan we're using, I'm truing to recreate the same behaviour in postman)
https://login.microsoftonline.com/{tenant_guid}/oauth2/v2.0/authorize?client_id={myclient_id_urlencoded}&response_type=code&redirect_uri={redirect_uri_urlencoded}&scope=offline_access%20https%3A%2F%2Fgraph.microsoft.com%2Fuser.read&state=12345
Then with the code in query string, i POST to the token endpoint:
POST /{tenant_guid}/oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com
Content-Type: application/x-www-url-form-urlencoded
cache-control: no-cache
Postman-Token: a0456a8d-6979-491f-b61e-86b5d614c577
client_id={myclient_id_urlencoded}
scope=https%3A%2F%2Fgraph.microsoft.com%2Fuser.read
redirect_uri={redirect_uri_urlencoded}
grant_type=authorization_code
client_secret={client_secret_urlencoded}
code=OAQABAAIAAADCoMpjJXrxTq9VG9te-7FXujKZhF...
I receive back an accesso token (that is working like a charm in accessing https://graph.microsoft.com/v1.0/me for an hour) and a refresh token. I would love to get a new pair of access/refresh token when the original access token expires, using the refresh_token grant_type
POST /tenant_guid/oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com
Content-Type: application/x-www-url-form-urlencoded
cache-control: no-cache
Postman-Token: 5d71f813-768e-476c-a97f-c109fba3165e
client_id={myclient_id_urlencoded}
scope=https%3A%2F%2Fgraph.microsoft.com%2Fuser.read
redirect_uri={redirect_uri_urlencoded}
grant_type=refresh_token
cclient_secret={client_secret_urlencoded}
refresh_token=OAQABAAAAAADCoMpjJXrxTq9VG9te-7FX8m6YMg-.....
But no matter if I try before access token expiration or after, closing and reopening postman, I always receive that error back. I've done almost 50 tests (always with the full round of login/authorization to use always a fresh refresh token) with no luck.
Seems like I'm missing something really stupid here because I can't imagine that everybody else is behaving correctly... but really can't find a way out!
So I just got mine working! Here are the required parameters I needed:
client_id = your client id
refresh_token = the refresh token here
grant_type = refresh_token,
client_secret = secret
NOTE: Everything I read told me to URLEncode the values. I found it worked with them UNENCODED - no idea if it will really make a difference or not. Since it is going in the body of the post, which means it is TLS encrypted.
The other important thing was the url I posted to. There seem to be so many examples and none seem to be consistent. I used this format:
https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token
The last thing is to be sure you are using the correct app id. In my case I was using the appId for the wrong app and it didnt have consent. Hope this helps. I do wish Microsoft would make a concerted effort to spell out things consistently and think like someone who doesn't do security for a living.
Finally resolved thanks yo the Azure Support.
The problem is the client id: as I supposed before, Microsoft allow you to define another application name, but always want to use the GUID client id to submit any request. Unfortunately, it was warning me when I didn't url-encoded it, but did not alert me that it was not correct until I tried to use the refresh token.
So just read very very well the documentation: client_id: The Application (client) ID that the Azure portal – App registrations experience assigned to your app (so not the one you choose).
https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
Maybe including a format validation in the documentation would help!

Url's containing authentication secrets and app ID's

We received a request to create a REST api. I was a little confused in the example of provided by our client. As you can see below, they've identified the app_id and secret in the URL before the #. The remainder of the URI looks like what I would expect.
Is this valid? I thought maybe this is some weird cURL format I haven't seen before.
https://{application_id}:{api_secret}#api.example.com/entity/{entity_id}/
https://{application_id}:{api_secret}#api.example.com/entity/{entity_id}/entity_locations/{locations_id}/
Just seeing if anyone has seen this format before?
A URI is made up of various parts, one of them being the authority part, which can feature optional username:password element.
The full scheme is:
scheme://username:password#domain:port/path?query_string#fragment_id
This way your REST api remains stateless [not relying on previous app states like storing stuff in session]. But I advice you not to explicitly go with the username:password#stuff route, but to rely on Basic HTTP Auth, so the credentials are sent encoded in Base64 at least.
EDIT: a brief note about BasicAuth now you're asking - things go like this:
you make a request to http://johndoe:12345#service/api/foo/bar;
are credentials good? Ok, you get a 200 OK response with proper body;
are they not? You get a 401 Unauthorized response.
In the latter case, it's the browser [or any other program / script performing the request] that should prompt the user with the login popup.
Usually browsers ask you to cache credentials not to ask them every time, but this does not mean that they are not sent - it's just that every request to protected resources are featured with such header:
Authorization Basic base64encode(username:password)
Where base64encode is your custom way to encode the username:password string.

Place API key in Headers or URL

I'm designing a public API to my company's data. We want application developers to sign up for an API key so that we can monitor use and overuse.
Since the API is REST, my initial thought is to put this key in a custom header. This is how I've seen Google, Amazon, and Yahoo do it. My boss, on the other hand, thinks the API is easier to use if the key becomes merely a part of the URL, etc. "http://api.domain.tld/longapikey1234/resource". I guess there is something to be said for that, but it violates the principle of the URL as a simple address of what you want, and not how or why you want it.
Would you find it logical to put the key in the URL? Or would you rather not have to manually set HTTP headers if writing a simple javascript frontend to some data?
It should be put in the HTTP Authorization header. The spec is here https://www.rfc-editor.org/rfc/rfc7235
If you want an argument that might appeal to a boss: Think about what a URL is. URLs are public. People copy and paste them. They share them, they put them on advertisements. Nothing prevents someone (knowingly or not) from mailing that URL around for other people to use. If your API key is in that URL, everybody has it.
It is better to use API Key in header, not in URL.
URLs are saved in browser's history if it is tried from browser. It is very rare scenario. But problem comes when the backend server logs all URLs. It might expose the API key.
In two ways, you can use API Key in header
Basic Authorization:
Example from stripe:
curl https://api.stripe.com/v1/charges -u sk_test_BQokikJOvBiI2HlWgH4olfQ2:
curl uses the -u flag to pass basic auth credentials (adding a colon after your API key will prevent it from asking you for a password).
Custom Header
curl -H "X-API-KEY: 6fa741de1bdd1d91830ba" https://api.mydomain.com/v1/users
passing api key in parameters makes it difficult for clients to keep their APIkeys secret, they tend to leak keys on a regular basis.
A better approach is to pass it in header of request url.you can set user-key header in your code .
For testing your request Url you can use Postman app in google chrome by setting user-key header to your api-key.
I would not put the key in the url, as it does violate this loose 'standard' that is REST. However, if you did, I would place it in the 'user' portion of the url.
eg: http://me#example.com/myresource/myid
This way it can also be passed as headers with basic-auth.