Re-using access token
coolapso opened this issue · 5 comments
Apologies if this has been answered here, but I am having some trouble with authentication, wonder what I could be missing and if you can point me in the right direction
I'm writing a CLI application, and it contains a "configure" action myapplication configure
that:
- Asks for mastodon server
- Registers the app
- requests for the access token
- Saves ID, Secret, and Token to a configuration file to be re-used Later
Then an Action to create a post myapplication -m "text"
- Loads the settings & Secrets from configuration file (ClientID, ClientSecret, AccessToken)
- Uses the values to create a new mastodon client config and pass it to the
NewClient()
- Authenticates Access token
- Creates a post
Now, the problem with this is:
I am able to create a post After generating a configuration file, the next time I try to make a post re-using the exact same accessToken, I will get Invalid_grant
If I move the Authenticate access token
to the configuration step (before saving everything into the configuration file), When I try to create a post I get The access token is invalid
.
☸ virt01 ❯go run main.go configure
Mastodon Server (https://mastodon.social):
Open your browser to
https://mastodon.social/oauth/authorize?client_id=xxxxxxxx&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code&scope=read+write+follow
and copy/paste the given token
Paste the token here:xxxxxxxxxx
[coolapso@nebu]-[~/megophone] dev go v1.23.2 15s
☸ virt01 ❯go run main.go -m "test"
Posting...
Toot created with ID: 113301936934081496
Done!
[coolapso@nebu]-[~/megophone] dev go v1.23.2
☸ virt01 ❯go run main.go -m "test"
Posting...
Mastodon Authentication failed, bad authorization: 400 Bad Request: invalid_grant
exit status 1
Do I need to request users to copy pate the link get a new token and paste and Authenticate the token every time I want to make the post?
I took a look at this issue and it's mentioned in this comment that AuthenticateToken()
only needs to be used once, But it seems the token becomes invalid as soon as it is used once, and its not accepted if its authenticated and then re-used on another "session" of the same application.
Genuinely confused.
Thanks a lot for your time and help!
Giving also some bit more info, in the "semi working" scenario ....
This works:
func mastodonClientConfig() *gomasto.Config {
return &gomasto.Config{
Server: viper.GetString("mastodon_server"),
ClientID: viper.GetString("mastodon_client_id"),
ClientSecret: viper.GetString("mastodon_client_secret"),
AccessToken: viper.GetString("mastodon_access_token"),
}
}
func postMastodon(text, mediaPath string) (err error) {
config := mastodonClientConfig()
client := gomasto.NewClient(config)
if mediaPath != "" {
return
}
if err := client.AuthenticateToken(context.Background(), config.AccessToken, redirectUri); err != nil {
return fmt.Errorf("Mastodon Authentication failed, %v\n", err)
}
id, err := mastodon.CreatePost(context.Background(), client, text, "public")
if err != nil {
return fmt.Errorf("Failed to post to mastodon, %v\n", err)
}
fmt.Println("Toot created with ID:", id)
return nil
}
This not doesn't (shouldn't it be the same thing??):
func mastodonClientConfig() *gomasto.Config {
return &gomasto.Config{
Server: viper.GetString("mastodon_server"),
ClientID: viper.GetString("mastodon_client_id"),
ClientSecret: viper.GetString("mastodon_client_secret"),
AccessToken: viper.GetString("mastodon_access_token"),
}
}
func authenticateToken(ctx context.Context, accessToken string) error {
client := gomasto.NewClient(mastodonClientConfig())
return client.AuthenticateToken(ctx, accessToken, redirectUri)
}
func postMastodon(text, mediaPath string) (err error) {
config := mastodonClientConfig()
client := gomasto.NewClient(config)
fmt.Println(client.Config)
fmt.Println(client.UserAgent)
if mediaPath != "" {
return
}
if err := authenticateToken(context.Background(), c.m.GetAccessToken()); err != nil {
return fmt.Errorf("Failed to authenticate access token, %v\n", err)
}
id, err := mastodon.CreatePost(context.Background(), client, text, "public")
if err != nil {
return fmt.Errorf("Failed to post to mastodon, %v\n", err)
}
fmt.Println("Toot created with ID:", id)
return nil
}
It looks like the requests have to always be made by the same client that authenticated, otherwise it doesn't work, even tho the clients look exactly the same and use exactly the same configurations 🤔 so confused.
Hey, here's what I think the issue is ... unless I am understanding this wrong, I believe this is actually a bug in the library and a unfortunate naming issue.
TL;DR
the c.authenticate()
method, is broken. It should return the access token in order to save it, but instead sets it in the current instance of the client.
AuthenticateToken
is an unfortunate naming, because when calling c.authenticate
what it is actually doing is to exchange an authorization code for an AccessToken
The Long road
When using oauth authentication with mastodon the flow is as follow:
- Register the app and get in exchange a
client_id
and aclient_secret
- Craft the URL and initiate the user authroization grant in exchange for a (and here is the key word), access code
- Use the
client_id
and theclient_secret
to exchange that access code with aaccess token
- Save that access token and use it to do whatever your app wants to do
Here's What is happening in the the go-mastodon library, and how things are named.
- the
RegisterApp()
method, registers the application and returns theclient_id
and theclient_secret
(so far so good) - the
AuthenticateToken()
returns only aerror
when it should be returning the actual access token.
Looking deeper into the code we can see that AuthenticateToken()
returns c.authenticate()
and that c.authenticate
actually makes the request to the mastodon oauth/token
endpoint, which according to the mastodon documentation here is used to obtain the Access Token and not to "authenticate" the token.
Here's the curl example:
curl -X POST \
-F "client_id=${CLIENT_ID}" \
-F "client_secret=${CLIENT_SECRET}" \
-F 'redirect_uri=urn:ietf:wg:oauth:2.0:oob' \
-F 'grant_type=authorization_code' \
-F 'code=********************************' \
-F 'scope=read write push' \
https://mastodon.social/oauth/token
{"access_token":"*********************","token_type":"Bearer","scope":"read write push","created_at":1729194979}%
this being said the c.authenticate()
method should be returning both a error and the token string, so it could be saved and be re-used but instead it is setting the AccessToken only in the current instance of the client which explains why it works the first time and not the second time and why it works in one side of the code and not in the other.
a) The client is effectively different
b) every time we call the AuthenticateToken we are actually trying to exchange the the access code by a token, however that access code will be already invalided because it was already used to exchange for a token.
The work around
Until a PR is accepted and merged, after using AuthenticateToken the value that should be saved is the access token in the current instance of the client. accessToken := client.Config.AccessToken
Got bit by this aswell.
The README.md code excerpt should show how to fetch (without necessarily stating how to store) the AccessToken.
Changing authenticate
's return values would bubble up in several other function signatures and this would be somewhat breaking.
I am planning to put up a PR with a RFC with a fix for this during this week.
Changing the method is definitely not a great idea ... but my thoughts are around marking the method as deprecated,, to create the necessary methods to fix this issue and of course adjust all the documentation regarding this. This should ensure backwards compatibility and allow the improvement of the whole authentication flow.