Following the docs at https://developer.plex.tv/pms/#section/API-Info/Authenticating-with-Plex I get everything going fine until I hit the last step and try exchanging my PIN for a JWT Token
Using the link page at https://plex.tv/link/?pin= and polling the Pin ID to find out when it’s been linked I end up with what looks like a Legacy authToken in the JSON
I still wanna use the JWT so I try exchanging my PIN for a JWT via a GET https://clients.plex.tv/api/v2/pins/?deviceJWT= BUT while it says in the docs that I will get a new JWT authToken I have exactly the same authToken that was visible during the polling.
A JWT should start with eyJ but my authToken(s) don’t
Checking my Plex server with the Token shows that it appears to work (BUT it’s not a JWT!!!)
Sorry for the slow reply, this one slipped through the cracks.
The behavior you’re describing usually means the pin poll fell back to the legacy token. That fallback is silent: if the pin wasn’t created with a jwk, or the deviceJWT on the poll is empty or can’t be matched to a registered key, the request still succeeds and authToken contains a legacy token instead of a JWT. No error is raised, which makes it easy to miss. That token is still valid, which is why it works against your server.
For the poll to return a proper eyJ... JWT, two things need to be in place:
- The pin is created with your device’s public key. Include a
jwk object in the body of POST https://clients.plex.tv/api/v2/pins (Ed25519/EdDSA, with a kid). When the user completes the link, that key gets registered to the device.
- The poll includes a signed device JWT. On
GET ``https://clients.plex.tv/api/v2/pins/{id}?deviceJWT=``..., the deviceJWT is signed with the private key matching the JWK from step 1, with aud: "``plex.tv``", iss: <your X-Plex-Client-Identifier>, and the same kid in the JWT header. No nonce is needed in this flow.
The full flow is documented under Authenticating with Plex (Option 1), in case it helps to cross-check your requests against it. The silent fallback above isn’t called out in the docs yet, which I suspect is what made this confusing.
If you already have a working legacy token, there’s also a second path that skips pins entirely: register your JWK with POST /api/v2/auth/jwk (authenticated with the legacy token), then use the nonce flow (GET /api/v2/auth/nonce, sign a device JWT including the nonce, POST /api/v2/auth/token) to get the 7-day JWT and every refresh after it. That’s Option 2 on the same docs page.
If neither of these matches what you’re seeing, happy to dig further. Sharing the request/response pair from your POST /pins call (minus any tokens) would help narrow it down.