trakt / api-help

Trakt API docs at https://trakt.docs.apiary.io
186 stars 7 forks source link

How to force user to log in again? OAuth is completing transparently #149

Open elicwhite opened 4 years ago

elicwhite commented 4 years ago

I have OAuth working in my app, but I can't seem to figure out how to make logging out work. When they press log out I revoke the token through the API and then clear my app state. When the user chooses to log back in, the oauth authentication screen pops up but still instantly closes.

This issue is the same as in this issue: https://github.com/openid/AppAuth-iOS/issues/281

traktsignout

They recommend adding prompt=login to the request as that seems to be part of the OAuth spec for the server to force the user to enter credentials again. However, either I haven't implemented that correctly, or the Trakt API doesn't support it. 😀 Here is another article about that: https://www.oauth.com/oauth2-servers/authorization/requiring-user-login/

elicwhite commented 4 years ago

This issue is interesting and definitely related: https://github.com/trakt/api-help/issues/19#issuecomment-457733626

The recommendation is to authenticate against the trakt.tv endpoint and not api.trakt.tv. This is a good gotcha, every JS library for Trakt I've seen authenticates against api.trakt.tv.

Regardless, from that issue it seems like I would still need to manually hit /logout. Unfortunately I don't think I can open a browser with the same cookie jar to be able to call /logout since the iOS authentication framework is separate.

I think I'm out of ideas

rectifyer commented 4 years ago

Can you post the example code for how you are revoking the token via that API method? https://trakt.docs.apiary.io/#reference/authentication-oauth/revoke-token/revoke-an-access_token

I think its ok for the user to be logged into the Trakt website. Revoking the token should give them a prompt to allow access again for your app though. So, that's why I wondering if the revoke call has a bug in your implementation or if its something else going on.

elicwhite commented 4 years ago

Thanks for the response!

I'm revoking the token with this code:

const req = {
      method: 'POST',
      url: 'https://trakt.tv/oauth/revoke',
      headers: {
        'User-Agent': this._settings.useragent,
        'Content-Type': 'application/json',
        Authorization: `Bearer ${this._authentication.access_token}`,
        'trakt-api-version': '2',
        'trakt-api-key': this._settings.client_id,
      },
      body: JSON.stringify({
        token: this._authentication.access_token,
        client_id: this._settings.client_id,
      }),
    };

    return fetch(req.url, req);

I ran this against the apiary debug url endpoint and it seemed like it was happy with my request shape.

elicwhite commented 4 years ago

I've also tried hitting the api endpoint, as well as trying to include my client_secret in the body.

rectifyer commented 4 years ago

Please try using api.trakt.tv for the hostname and sending client_secret as well, basically match the docs exactly. The trakt.tv hostname should only be used to generate the authorize URL, but not for the other API methods.

elicwhite commented 4 years ago

My new request is this:

const req = {
      method: 'POST',
      url: `https://api.trakt.tv/oauth/revoke`,
      headers: {
        'User-Agent': this._settings.useragent,
        'Content-Type': 'application/json',
        Authorization: `Bearer ${this._authentication.access_token}`,
        'trakt-api-version': '2',
        'trakt-api-key': this._settings.client_id,
      },
      body: JSON.stringify({
        token: this._authentication.access_token,
        client_id: this._settings.client_id,
        client_secret: this._settings.client_secret,
      }),
    };

I still get a json response from the server of {}, and the result is the same. The next authentication is still resolved immediately and transparently without the user being prompted for anything. 😢

rectifyer commented 4 years ago

I looked into the prompt option more and the OAuth library version we're using doesn't currently support it, but it looks like a newer version does. This is one our roadmap to upgrade, but I don't have an ETA yet.

The server will always respond with a 200 success when revoking, so it is not a reliable way to know for sure a token was actually removed. I believe this is done for security reasons and so you can't use that endpoint to actually verify if a token is valid or not.

Your code looks correct, so my guess is the token is revoked ok, but there might be additional valid tokens that exist for the app connection. This could happen if a user connects the same app on multiple devices for example. I wonder if that is what's going on here.

elicwhite commented 4 years ago

Thanks for the update! I actually have an update from my end too!

I realized yesterday that the authentication popup is using the same cache as the regular Safari WebView. So if I open my app and sign in with trakt, then open Safari on the phone, I'm signed in on trakt too. If I sign out of the app, then sign back in it does the immediate sign in thing I reported in the OP.

However, if I'm signed in on the app, and then sign out through the app (revoking the token), then open Safari and log out from trakt's website, then the next time I sign in on the app it asks for the credentials.

So maybe if iOS will let me pop an offscreen webview to trakt.tv/logout, I can make that experience work.

It would still be great to get prompt support, but it is at least helpful to have a better understanding of the iOS system! 😀

rectifyer commented 4 years ago

Good info, thank you! Yes, we plan to add in prompt support once we're able to upgrade the OAuth library. We'll make sure to announce that and update the API docs accordingly.

rectifyer commented 4 years ago

I haven't forgotten about this, we just haven't updated our OAuth library yet which is required to support this. I'm hoping in the next month or so we'll be able to add support for this.

elicwhite commented 4 years ago

Wow, I appreciate the follow up. FWIW, it would have been totally reasonable for you to have forgotten about this and never thought about it again. I'm one of the maintainers of a pretty big project and there is nothing I can do to avoid that from happening. I appreciate the care and attention.

rectifyer commented 4 years ago

I took a deeper look at this and it appears prompt is part of OpenID which we don't currently use. I thought our code OAuth library had support, but it looks like only if we add in OpenID support. So basically, is this part of the core OAuth spec?

elicwhite commented 4 years ago

That's possible, I'm sure you are significantly more informed than I am. I haven't done that much with OAuth or OpenID before. A cursory Google only seems to mention it connected to OpenID, so seems like yes.

kawa89 commented 4 years ago

👋 Any update on this? This happens also on Android. Same reason as in iOS - the CustomTabs share cache with Chrome.

lswee commented 1 year ago

Using AppAuth-iOS, if you make the Safari session ephemeral (like incognito), then it won't remember the user's details each time.

Something like: OIDAuthState.authState(byPresenting: request, presenting: viewController, prefersEphemeralSession: true)

Perhaps you could do something similar on Android too? https://github.com/openid/AppAuth-Android

rectifyer commented 1 year ago

Sorry for the extreme delay on this, but I ended up just coding this functionality myself. The update will be released tomorrow and the API docs are updated at https://trakt.docs.apiary.io/#reference/authentication-oauth/authorize-application.

You can send ?prompt=login in the authorize URL to force the user to sign in (it destroys the session of any currently signed in user).