Skip to content
This repository has been archived by the owner on May 13, 2024. It is now read-only.

Invalid or expired jwt #1

Open
Toyro98 opened this issue Aug 30, 2023 · 15 comments
Open

Invalid or expired jwt #1

Toyro98 opened this issue Aug 30, 2023 · 15 comments

Comments

@Toyro98
Copy link

Toyro98 commented Aug 30, 2023

I'm trying to find a way to get my data so I don't have to unlock my phone and open the libre app to see what my blood sugar level is on.

This unofficial api is the closest I've gotten so far. Logging in and getting the token works perfectly, however I can't accept the terms of use since my jwt is "invalid or expired". It can't be expired since the unix timestamp is set to 6 months ahead?

The response I get after using the auth/continue/tou with the token

{"message":"invalid or expired jwt"}

Here's the code I use to send the request. I tried with api.libreview instead of api-eu.libreview. Still the same error message. Has the api changed or what am I doing wrong?

And yes, I update the token manually after the login, this was a quick way to test and if I got it to work, I'd make it better

static readonly string token = "";

static async Task Main(string[] args)
{
    //await Login();
    await AcceptTerms();

    Console.ReadLine();
}

static async Task Login()
{
    var client = new HttpClient();
    var request = new HttpRequestMessage
    {
        Method = HttpMethod.Post,
        RequestUri = new Uri("https://api-eu.libreview.io/llu/auth/login"),
        Headers =
        {
            { "version", "4.7" },
            { "product", "llu.android" },
            { "Accept", "application/json" },
        },
        Content = new StringContent("{\"email\": \"...\", \"password\": \"...\"}")
        {
            Headers =
            {
                ContentType = new MediaTypeHeaderValue("application/json")
            }
        }
    };

    using var response = await client.SendAsync(request);
    var body = await response.Content.ReadAsStringAsync();
    Console.WriteLine(body); 
}

static async Task AcceptTerms()
{
    var client = new HttpClient();
    var request = new HttpRequestMessage
    {
        Method = HttpMethod.Post,
        RequestUri = new Uri("https://api-eu.libreview.io/auth/continue/tou"),
        Headers =
        {
            { "version", "4.7" },
            { "product", "llu.android" },
            { "Accept", "application/json, application/xml" },
            { "Authorization", $"Bearer {token}" },
        },
        Content = new StringContent("{\"email\": \"...\", \"password\": \"...\"}")
        {
            Headers =
            {
                ContentType = new MediaTypeHeaderValue("application/json")
            }
        }
    };

    using var response = await client.SendAsync(request);
    var body = await response.Content.ReadAsStringAsync();
    Console.WriteLine(body);
}
@FokkeZB
Copy link
Owner

FokkeZB commented Sep 1, 2023

Hey @Toyro98 - thanks for using the docs, although I still need to give it is final push and then also announce it in the various communities.

I recently implemented automatically accepting TOU in GlucoseDirect via https://github.com/creepymonster/GlucoseDirect/pull/550/files - you may want to take a look at that.

  • You should not send any data for the tou request.
  • You should only need to make the tou request when the response to the login request includes a status of 4.

@Toyro98
Copy link
Author

Toyro98 commented Sep 5, 2023

Hey @FokkeZB, thanks for creating the docs. I tried sending no data to the tou request as you said, still the same message. I looked into the graph and I got my id from login request which is the one above "firstName". Sent a request and get back this message

{"status":4,"error":{"message":"followerNotConnectToPatient"}}

I checked the github repo and found this LibreLinkUpConnection.swift#L411-L445. No error handling for status 4. In the code, I see comments that status code 4 means that I need to accept tou? Which for some reason I can't since my token is "invalid".

I also see frequent mention of "LibreLinkUp". I downloaded the app and created an account and connected it to my Libre and could see graph history there. Tried the graph api and still get that message "followerNotConnectToPatient".

One thing I've noticed in the json I get from logging in is this

"consents": {
    "realWorldEvidence": {
        "policyAccept": 1670427701,
        "touAccept": 0,
        "history": [
            {
                "policyAccept": 1670427701
            }
        ]
    }
}

Is the reason that I can't get graph data is due to the "touAccept" being 0? But trying to accept tou gives me invalid token as response. Any idea what I could have done wrong or missed an important step?

@FokkeZB
Copy link
Owner

FokkeZB commented Sep 6, 2023

You can find the handling of status 4 at:

https://github.com/FokkeZB/GlucoseDirect/blob/487619466877d0d7439e989b81130bd824c39847/App/Modules/SensorConnector/LibreConnection/LibreLinkUpConnection.swift#L192-L193

So the flow is:

  1. Make the request to login
  2. If you get status 4 back, make the request to accept TOU
  3. Else or after, make any other request

Could you share the consecutive requests you're making (with the password censored)?

@Toyro98
Copy link
Author

Toyro98 commented Sep 6, 2023

The first request I make is this and I get data back from the api. I save the id (data.user.id) and token (data.user.authTicket.token) for further requests

private static async Task Login()
{
    var client = new HttpClient();
    var request = new HttpRequestMessage
    {
        Method = HttpMethod.Post,
        RequestUri = new Uri("https://api-eu.libreview.io/llu/auth/login"),
        Headers =
        {
            { "version", "4.7" },
            { "product", "llu.android" },
            { "Accept", "application/json" },
        },
        Content = new StringContent("{\"email\": \"" + email + "\", \"password\": \"" + password + "\"}")
        {
            Headers =
            {
                ContentType = new MediaTypeHeaderValue("application/json")
            }
        }
    };
}

I would send a request to accept the tou. (I now know I should send this once I get status 4 on the login) This api sends this back to me {"message":"invalid or expired jwt"}

I expect it to return data but I don't get data back. In the documents it says "Log in is successful and terms have been accepted. The body contains the user object and token to use for subsequent requests."

private static async Task AcceptTerms()
{
    var client = new HttpClient();
    var request = new HttpRequestMessage
    {
        Method = HttpMethod.Post,
        RequestUri = new Uri("https://api-eu.libreview.io/auth/continue/tou"),
        Headers =
        {
            { "Authorization", $"Bearer {token}" },
        }
    };

    using var response = await client.SendAsync(request);
    var body = await response.Content.ReadAsStringAsync();
    Console.WriteLine(body);
}

Now I'm trying to get the graph data which gives me a {"status":4,"error":{"message":"followerNotConnectToPatient"}}

private static async Task GetGraph()
{
    var client = new HttpClient();
    var request = new HttpRequestMessage
    {
        Method = HttpMethod.Get,
        RequestUri = new Uri($"https://api-eu.libreview.io/llu/connections/{id}/graph"),
        Headers =
        {
            { "product", "llu.android" },
            { "version", "4.7" },
            { "Accept", "application/json" },
            { "Authorization", $"Bearer {token}" },
        },
    };

    using var response = await client.SendAsync(request);
    var body = await response.Content.ReadAsStringAsync();
    Console.WriteLine(body);
}

@FokkeZB
Copy link
Owner

FokkeZB commented Sep 11, 2023

Mmm, I get the same thing when I do this in a terminal:

curl -XPOST -H 'version: 4.7' -H 'product: llu.android' -H 'Accept: application/json' -H "Content-type: application/json" -d '{"email":"[email protected]","password":"PASSWORD"}' 'https://api-eu.libreview.io/llu/auth/login'
curl -XPOST -H 'version: 4.7' -H 'product: llu.android' -H 'Accept: application/json' -H 'Authorization: Bearer TOKEN' 'https://api-eu.libreview.io/auth/continue/tou'

But I wonder if that is because the first request does not return status 4, so I don't need to make the TOU request, and perhaps the "invalid or expired jwt" is just their way of saying that.

Because when I instead do:

curl -XGET -H 'version: 4.7' -H 'product: llu.android' -H 'Accept: application/json' -H 'Authorization: Bearer TOKEN' 'https://api-eu.libreview.io/llu/connections'

That works fine and returns my connections.

I have no idea how to force status 4 on login, so I'd only be able to reproduce when I run into it again.

@mattgoff
Copy link

mattgoff commented Feb 7, 2024

@Toyro98 Did you ever find a solution to this, I seem to be encountering something similar.

When I auth I get back a jwt with a status of 0.
{ "status": 0, "data": { "user": { "id": "censored", "firstName": "censored", "lastName": "censored", "email": "censored", "country": "US", "uiLanguage": "en-US", "communicationLanguage": "en-US", "accountType": "pat", "uom": "1", "dateFormat": "1", "timeFormat": "1", "emailDay": [ 1 ], "system": { "messages": { "firstUsePhoenix": 1707268443, "firstUsePhoenixReportsDataMerged": 1707268443, "lvWebPostRelease": "3.16.19" } }, "details": {}, "twoFactor": { "primaryMethod": "", "primaryValue": "", "secondaryMethod": "", "secondaryValue": "" }, "created": "censored", "lastLogin": "censored", "programs": {}, "dateOfBirth": "censored", "practices": {}, "devices": { "censored": { "id": "censored", "nickname": "FirstDevice", "sn": "censored", "type": 40068, "uploadDate": "censored" } }, "consents": { "hipaa": { "policyAccept": 1707268443, "touAccept": 0, "history": [ { "policyAccept": 1707268443 } ] }, "realWorldEvidence": { "policyAccept": 1707332498, "declined": true, "touAccept": 0, "history": [ { "policyAccept": 1707268443, "declined": true }, { "policyAccept": 1707329182 }, { "policyAccept": 1707332498, "declined": true } ] } } }, "messages": { "unread": 0 }, "notifications": { "unresolved": 0 }, "authTicket": { "token": "censored", "expires": "censored", "duration": 15552000000 }, "invitations": null, "trustedDeviceToken": "" } }

If I try to accept the tou (even though my status is 0) I get:
{"message":"invalid or expired jwt"}

and then when I call to
the connections endpoint I get: (notice that data is empty)
{"status":0,"data":[],"ticket":{"token":"censored,"expires":1722886492,"duration":15552000000}}

and if I call to the graphs endpoint I get:
{"status":4,"error":{"message":"followerNotConnectToPatient"}}

@Toyro98
Copy link
Author

Toyro98 commented Feb 9, 2024

I did not find a solution to this

@sgmoore
Copy link

sgmoore commented Feb 12, 2024

Don't know whether this will be helpful, but I managed to get a graph result.
Unfortunately because the path is not the same every time, I don't know if this is repeatable, but I will detail the steps I took.

  1. Sent a HttpMethod.Post to https://api-eu.libreview.io/llu/auth/login.
  2. This returned a redirect response tellling me to use eu2.
  3. Sent a HttpMethod.Post to https://api-eu2.libreview.io/llu/auth/login.
  4. This returned {"status":4,"data":{"step":{"type":"tou","componentName":"AcceptDocument","props":{"reaccept":true,"titleKey":"Common.termsOfUse","type":"tou"}}, ... ,"authTicket":{"token":" ...
  5. Sent a HttpMethod.Post to https://api-eu2.libreview.io/auth/continue/tou using the token from step 4
  6. This returned {"status":4,"data":{"step":{"type":"pp","componentName":"AcceptDocument","props":{"reaccept":true,"titleKey":"Common.privacyPolicy","type":"pp"}} ... ,"authTicket":{"token":" ...
  7. Sent a HttpMethod.Post to https://api-eu2.libreview.io/auth/continue/pp using the token from step 6 (which was different)
  8. This returned {"status":0,"data":{"user":{"id":" ... ,"authTicket":{"token":" ...
  9. Sent a HttpMethod.Get to https://api-eu2.libreview.io/llu/connections/{id}/graph where {id} is the user id from Step 8 and using the token from step 8

@FokkeZB
Copy link
Owner

FokkeZB commented Feb 16, 2024

@sgmoore thanks for figuring that out!

So it seems that we can abstract this responding to any status 4 response by doing a POST to https://api-eu2.libreview.io/auth/continue/{data.step.type} using the last received token, until we get a different status (0), and can continue with the intended request.

I don't have time to update this in the documentation atm, but would welcome a PR.

@opsb
Copy link

opsb commented Feb 21, 2024

I'm seeing something a bit different here (I'm also in eu2):

  • Step 3 immediately returns a status of 0 but the response also contains user.consents.realWorldEvidence.touAccept = 0 which seems to imply the tou haven't been accepted.
  • If I try to call get_graph then I get the {"error": {"message": "followerNotConnectToPatient"}} response.
  • If I try to accept the tou or pp using the token returned from step 3 then I get the {"message": "invalid or expired jwt"} response.

edit: looks like I'm seeing the same as @mattgoff

@mattgoff
Copy link

Here are some of my notes:

My spouse has the CGM and runs the Libre app on her phone. In the app I had her send me a request to be able to view her data. This required me to setup an account and accept her request and install the LibreLinkUp app.

Accessing the Libreview api this way gives me access to my Libre connections (one of which is my spouse)

Here's a link to the code that I'm using to pull her data and then send it off to another API endpoint:

https://github.com/mattgoff/libre_cgm/blob/main/get_libre.py

The process looks like:

  1. login with my account and get the oath token
  2. then I call the graph endpoint with HER userid in the url: https://api-us.libreview.io/llu/connections/{USERID}/graph

At this point I have her glucose data.

I've only ever accepted the tou in the web interface when I was setting up my account. I've never had to interact with this endpoint to get things working.

@opsb
Copy link

opsb commented Feb 21, 2024

Thanks @mattgoff, I really appreciate you sharing your solution.

@opsb
Copy link

opsb commented Feb 21, 2024

Well it does seem to be the case that you aren't able to fetch your own glucose readings, only those of others that have shared their data with you. While a bit surprising it does make sense from the point of view of the LibreLinkUp app which is intended for following others.

@opsb
Copy link

opsb commented Feb 22, 2024

I had another look at this today and confirmed the above, you can access your own data but only if you follow yourself from the LibreLinkUp app.

@sgmoore
Copy link

sgmoore commented Feb 22, 2024

I had another look at this today and confirmed the above, you can access your own data but only if you follow yourself from the LibreLinkUp app.

It depends on what you are looking for. AFAIK you need to follow yourself if you want to get your most recent blood glucose value - and that is what the OP was wanting to do and probably what most of us want as well.

But you can access some of your own data even if you haven't followed yourself.

For example I can call https://api-eu2.libreview.io/glucoseHistory?numPeriods=5&period=14 and get summary details for the last five fortnights including the average glucose , maximum glucose, number of hypos and other details which I assume are used to draw the graphs displayed when you log into the libreview website.

It would be a lot more complicated, but if you really wanted to you could probably generate and download the pdf report. And you could probably download your whole data to csv if you could find some way of getting a google captcha.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants