-
Hello, Recently, Bluesky added access tokens (#151), and right now, the library allows exporting a session string ( Previously, the refresh token had been implemented with a 2-month lifetime (and then developer should generate another refresh token with In my codebase, it's something like this: def app_init():
# On app init
client = Client()
client.login(BLUESKY_USER_HANDLE, BLUESKY_USER_APP_PASSWORD)
with open(BLUESKY_SECRET_FILE, "w") as f:
f.write(client.export_session_string())
def post_message(text):
session_string = "..." # content from file
client = Client()
client.login(session_string=session_string)
if client._access_jwt and client._should_refresh_session():
client._refresh_and_set_session()
# Update file with new session
with open(BLUESKY_SECRET_FILE, "w") as f:
f.write(client.export_session_string())
# client.send_post(...) My point here is that protected methods are being used to resolve this problem with autorefresh. Is it better to make them public (if the token needs to be invalidated in this way)? My initial expectation is that methods like |
Beta Was this translation helpful? Give feedback.
Replies: 16 comments 3 replies
-
Hi! But SDK cares about refreshing of access token using refresh token for a long time 🧐 was added here #27 The problem that I can understand that we must export session every 2 hours because of refreshed tokens 😢 Or do you want to say that auto-refresh logic was broken in SDK? |
Beta Was this translation helpful? Give feedback.
-
I mean that all methods are checking if the session is expired and renewing it if necessary |
Beta Was this translation helpful? Give feedback.
-
Aah, thanks! I'll recheck my implementation – looks like something wrong with tokens on my end.
Yeah, you're correct! I guess I can do something like this one:
But probably some more elegant way is needed 🤔 |
Beta Was this translation helpful? Give feedback.
-
We definitely should save the session to persistent storage at the end of the script. But we also must catch all errors first to not exit without saving the exported session. This doesn't fit well with envs, CI/CD secrets, and so on 🥲 sad |
Beta Was this translation helpful? Give feedback.
-
We can use the exported session for two months before updating it in persistent storage. Isn't it? |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
I'd assume that something is broken on bsky side with this token – with newly generated tokens everything works fine! I think we're good to use the same exported session for 2 months! |
Beta Was this translation helpful? Give feedback.
-
I can add the ability to register callback on token refresh. And the user can implement storing this token in db or file or wherever |
Beta Was this translation helpful? Give feedback.
-
Didn't you changed password or remove app password? |
Beta Was this translation helpful? Give feedback.
-
Nope. Furthermore, for a token that was generated yesterday, I'm getting the same "Token has been revoked" error. Password/handle haven't been changed, and I am still able to generate new tokens with the same password. Token generated at 1695150790, exp=1695157990 >>> client = Client()
>>> client.login(session_string=sec)
atproto.exceptions.BadRequestError: Response(success=False, status_code=400, content=XrpcError(error='ExpiredToken', message='Token has been revoked'), headers=Headers({'date': 'Wed, 20 Sep 2023 09:07:36 GMT', 'content-type': 'application/json; charset=utf-8', 'content-length': '59', 'connection': 'keep-alive', 'x-powered-by': 'Express', 'access-control-allow-origin': '*', 'ratelimit-limit': '3000', 'ratelimit-remaining': '2998', 'ratelimit-reset': '1695201121', 'ratelimit-policy': '3000;w=300', 'vary': 'Accept-Encoding'}))
...
>>> client._import_session_string(sec)
SessionString(...)
>>> client._should_refresh_session()
True
>>> client._refresh_and_set_session()
atproto.exceptions.BadRequestError: Response(success=False, status_code=400, content=XrpcError(error='ExpiredToken', message='Token has been revoked'), ...) |
Beta Was this translation helpful? Give feedback.
-
so... does it mean that when you refresh the session and get a new refresh token, the old one is revoked? |
Beta Was this translation helpful? Give feedback.
-
Can confirm; started using tokens yesterday and after just four login events (across four hours) the token was "revoked" with no further information. Making new sessions works, but it's not clear how long they will last and how fast I'm going to hit the rate limit. |
Beta Was this translation helpful? Give feedback.
-
Hi, I'm looking for any code snippet or something that handles tokens seamlessly and nicely. My idea would be to provide a login, pass and storage file for the token data. Then the login method would check the token validity, or try to refresh it, or simply login from scratch again, and save the token data on disk for future use (not sure if a file is the best idea but you get the point). All this process done in one method call, which returns an atproto.Client instance. |
Beta Was this translation helpful? Give feedback.
-
I still have no idea what I'm doing wrong; tokens just get revoked on a seemingly random basis and refreshing my token does not seem to work. I'm running v0.0.29. This code runs before every activation of my bot script: import os
import sys
from dotenv import load_dotenv
from atproto import Client
import time
import json
load_dotenv()
username = str(os.getenv('BSKY_UNAME'))
password = str(os.getenv('BSKY_APP_PASSWORD'))
session_file = "session.json"
time_valid = 7200 # 2h, in seconds
def getCurrentTime():
return int(time.time())
def getExpiryTime(creation_time):
# 30 days; atproto protocol session token has 2month validity
return creation_time + (time_valid)
def checkSessionValidity():
current_time = getCurrentTime()
try:
with open(session_file, 'r') as file:
data = json.load(file)
expiry_time = data['expiry']
created_time = data['created']
if expiry_time < current_time:
print(f"Session valid until {expiry_time} exceeds current time {current_time}. Generating new session token.")
createNewSession()
else:
print(f"Session token valid until {expiry_time} ({(expiry_time - current_time) / 3600} hours remaining)")
except json.JSONDecodeError as e:
print(f"{e}: JSON file invalid; file likely empty. Generating new session token.")
expiry_time = createNewSession()
print(f"Created new session, valid until {expiry_time} ({time_valid} seconds)")
def getSessionString():
try:
with open(session_file, 'r') as file:
data = json.load(file)
return data["session"]
except json.JSONDecodeError as e:
print(f"{e}")
return None
def createNewSession():
try:
client = Client()
client.login(username, password)
except Exception as e:
sys.exit(f"Bsky login failed: {e}")
session_string = client.export_session_string()
current_time = getCurrentTime()
expiry_time = getExpiryTime(current_time)
data = {
"created": current_time,
"expiry": expiry_time,
"session": session_string
}
with open(session_file, 'w') as file:
json.dump(data, file)
return expiry_time
if __name__ == "__main__":
print("Checking session validity..")
checkSessionValidity() Then, in my main script, we log in: #bsky api
if ENABLE_BSKY:
bsky_client = Client()
session_string = getSessionString()
try:
bsky_client.login(session_string=session_string)
except BadRequestError as e:
print(f"Bsky login failed: {e}")
else:
print("Bsky login succesful!") After just about a week of running perfectly (bot activates every hour, so the token gets refreshed ~12 times a day, well under the rate limit??), it suddenly gives me the Token Revoked error again, and, despite running the "refresh token" function before logging in on bsky, it just won't refresh the token again. The error I now get:
I legitimately have no idea why it's not working. I'm not hitting the rate limit, and I refresh my token in case it is invalid, what's going wrong? I've initially used a validity of 60 (and later, 30) days, but that just worked for a single day, so I opted for 2-hour tokens. Session token creation/refreshing seems broken, or maybe I just don't understand how it's supposed to work (in that case, the documentation could use a better example of using tokens). Anyone able to help? |
Beta Was this translation helpful? Give feedback.
-
I'm also getting a lot of random ExpiredToken errors when I know that the tokens are not expired. They were just created yesterday. Was anyone ever able to figure this one out? I don't want to create new sessions every single time because that is rate limited by IP, but if the tokens don't work either then I might have to. The error I'm getting is:
I've confirmed the refresh token has the following |
Beta Was this translation helpful? Give feedback.
-
I collected all my thoughts and knowledge about it on the dedicated docs page: https://atproto.blue/en/latest/atproto_client/auth.html Also, starting from v0.0.41 SDK provides a new callback to receive session string changes which will help you to write proper work and avoid "expired token" errors. tldr: from atproto import Client, SessionEvent, Session
client = Client()
@client.on_session_change
def on_session_change(event: SessionEvent, session: Session):
print(event, session) new advanced example: https://github.com/MarshalX/atproto/blob/main/examples/advanced_usage/session_reuse.py i hope it will help you all. mark as the answer for now |
Beta Was this translation helpful? Give feedback.
I collected all my thoughts and knowledge about it on the dedicated docs page: https://atproto.blue/en/latest/atproto_client/auth.html
Also, starting from v0.0.41 SDK provides a new callback to receive session string changes which will help you to write proper work and avoid "expired token" errors.
tldr:
new advanced example: https://github.com/MarshalX/atproto/blob/main/examples/advanced_usage/session_reuse.py
i hope it will help you all. mark as the answer for now