Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add logout #48

Closed
sharpsan opened this issue Oct 3, 2019 · 45 comments · Fixed by #216
Closed

Add logout #48

sharpsan opened this issue Oct 3, 2019 · 45 comments · Fixed by #216
Labels
help wanted Extra attention is needed

Comments

@sharpsan
Copy link

sharpsan commented Oct 3, 2019

Currently there is no way to end an active session via this library.

@MaikuB
Copy link
Owner

MaikuB commented Oct 3, 2019

Noted and mentioned here that I'm happy to look at a PR for this

@VictorsAlves
Copy link

i having this problem too. I need logout =D

@jhoward321
Copy link

Since logout might be provider specific, would it be possible to return the specific configuration? For example, I am using auth0 as my IDP which has an api endpoint for terminating a session. I can easily make the api call outside of this library, but I need the platform specific callback url which this library has available already. If there was an exposed method allowing me to retrieve the configuration, I would be able to make a logout call via http. Is this a good possible workaround @MaikuB ?

@MaikuB
Copy link
Owner

MaikuB commented Jan 15, 2020

Regarding getting the configuration, I don't believe so as this plugin is a wrapper around the native AppAuth SDKs, which doesn't support getting provider specific configuration. It's more aimed for those that follow certain standardised specs. It's also limited by what members are in the native class(es) used to represent the configuration.

As Auth0 supports OIDC, you could retrieve the configuration yourself and try making an API call to terminate the session as you suggested. Haven't tried this myself but I would think it should work

@majdi21
Copy link

majdi21 commented Jan 19, 2020

Hello @MaikuB,

I wanted to share the behaviour that I experienced. Currently, I have a simply flutter project which has a button that sends an AuthorizationRequest and then a TokenRequest. It works fine (tested on Android pixel 3 emulator). The first time I tap the button I get redirected to my login page, I authenticate and then I'm redirected back. So far so good.

If I tap the login button again, it does a quick trip but I don't get prompted for login. Makes sense.

The behaviour that is confusing me is that if I kill the app and restart it and tap the login button, I'm not being prompted to login. Somehow the tokens were cached somewhere. I understand that the tokens are still valid on my backend but the question is: where is the app caching the information? Is there a way to clear it?

Thank you

@MaikuB
Copy link
Owner

MaikuB commented Jan 19, 2020

where is the app caching the information? Is there a way to clear it?

It's your browser so you can clear the browser's cache

@rocboronat
Copy link

Hi @MaikuB! Just an idea. May you just add a way to clear the browser's cache? I mean, if we could "reset" the library without making an actual log out, it would be sufficient. In that way, you don't need to know anything about the real environment.

@MaikuB
Copy link
Owner

MaikuB commented Jan 23, 2020

@rocboronat what you're asking for isn't possible as far as i know. Each app essentially runs in a sandboxed environment. I would say it's a security flaw if there was a way to programmatically to manipulate a browser's cache via another app. Also note that this plugin is just a wrapper to another SDK that does all of the work around authentication

@acoutts
Copy link

acoutts commented Jan 29, 2020

For what it's worth I've observed this same behavior (caching of session / token) in the React Native AppAuth wrapper as well. I think it's an inherent issue in the Android/iOS layer with the way the webview is implemented as it shares session information with the system browser.

@codenamics
Copy link

Hello,

iOS

For me worked passing /logout endpoint instead of /authorized.

But after redirect to login OKTA page i do not know how to close it programatcily. i Need to hit Cancel to close webview/browser.

Future logout(id) async {
    try {
      final AuthorizationTokenResponse result =
          await appAuth.authorizeAndExchangeCode(
        AuthorizationTokenRequest(
          'xxxxxx',
          'com.okta.dev-xxxx:/callback',
          scopes: ['openid', 'profile', 'email', 'offline_access'],
          additionalParameters: {
            "id_token_hint": id,
          },
          serviceConfiguration: AuthorizationServiceConfiguration(
              "https://dev-xxxx.okta.com/oauth2/v1/logout",
              "https://dev-xxxx.okta.com/oauth2/v1/token"),
        ),
      );

      return result.accessToken;
    } catch (e) {
      print(e);
    }
  }

@hallidev
Copy link

@codenamics Did you ever solve your issue for iOS? Everything below works on Android, but I cannot for the life of me kill the browser cookies in iOS.

I'm using IdentityServer4, and rather than the redirect uri you're passing as a second argument, IdentityServer4 wants a post_logout_redirect_uri as an argument. You can provide this in additionalParameters.

My version of your snippet above looks like this:

    await _appAuth.authorizeAndExchangeCode(
      AuthorizationTokenRequest(
        _clientId,
        AppConfig.oidcCallbackUri,
        additionalParameters: {"id_token_hint": _idToken, "post_logout_redirect_uri": AppConfig.oidcCallbackUri},
        serviceConfiguration: AuthorizationServiceConfiguration(_endSessionUrl, _tokenUrl),
      ),
    );

Maybe this could help you?

Also, I tried your solution, but with IdentityServer4 rather than OKTA, my app just crashes (stops) immediately with the following error:

[DEVICE LOG] 2020-04-16 08:55:01.039163-0400  localhost Runner[18007]: (CoreFoundation) *** Terminating app due to uncaught exception 'Attempted to create a token exchange request from an authorization
response with no authorization code.', reason: 'Attempted to create a token exchange request from an authorization response with no authorization code.'

This makes sense as my /endsession endpoint wouldn't be returning a proper authorization response. I'm actually kind of surprised that it works for you with OKTA.

I'm getting pretty desperate to find a solution for this as just "forgetting" the tokens in my app isn't good enough. The browser keeps the auth cookies around, so when logging in again it just automatically uses the cookies and you never get a chance to actually hit the login screen.

I'm actually a bit shocked at how difficult this has turned out to be. I'm at a complete loss as to how to kill the browser cookies in iOS, and at this point it's about to hold up my production app release. I've also posted a (slightly) more detailed question here on StackOverflow with no answers yet:

https://stackoverflow.com/questions/61250964/calling-appauth-sign-out-endsession-endpoint-using-identityserver4-in-flutter

Can anyone on this thread help point me in the right direction? This seems like it should be so simple.

@codenamics
Copy link

the thing is that if you use routes as stated in my code it is JWT not cookies. so when you hit logout route you actuly revoke the token. so you should be logged out. also Try to push/clear screenstack to redirect to login screen

@mgalsina
Copy link

When you call method for authorizing and exchanges code, there is needed to add an additional parameter called "promptValues" with 'login' value. In this way, every time the login is made there is no value in the cache and it always asks for a new login.

I came to this solution from this post openid/AppAuth-Android#215 where it was commented about Logout and Delete browser history and OpenId docs

final AuthorizationTokenResponse result =
    await appAuth.authorizeAndExchangeCode(
      AuthorizationTokenRequest(
        your_client_id,
        your_localhost,
        promptValues: ['login'],
        discoveryUrl:
        your_discovery_url,
        scopes: [your_scopes],
      ),
    );

@hallidev
Copy link

hallidev commented Apr 18, 2020

@mgalsina You are an absolute hero. I can't believe I didn't come across this with all of the time I spent troubleshooting. I just tested and it works a charm.

I'd still love to know if there's a way to physically end a session for federated logout purposes, but for my use case now, this will work fine.

Thank you a thousand times!

@sharpsan
Copy link
Author

sharpsan commented Apr 18, 2020

@mgalsina You are an absolute hero. I can't believe I didn't come across this with all of the time I spent troubleshooting. I just tested and it works a charm.

I'd still love to know if there's a way to physically end a session for federated logout purposes, but for my use case now, this will work fine.

Thank you a thousand times!

This is how I'm logging out:

  ​// https://github.com/MicrosoftDocs/azure-docs/issues/34577​
  ​Future​<​AuthorizationResponse​>​ ​_logoutActiveDirectory​() ​async​ {
    ​AuthorizationResponse​ _response;
    ​try​ {
      ​String​ _authorizeEndpoint ​=​
          ​'https://{TENANT}.b2clogin.com/{TENANT}.onmicrosoft.com/B2C_1_SignUpIn/oauth2/v2.0/logout'​;
      ​const​ ​String​ _tokenEndpoint ​=​
          ​'https://{TENANT}.b2clogin.com/{TENANT}.onmicrosoft.com/B2C_1_SignUpIn/oauth2/v2.0/token'​;
      ​AuthorizationServiceConfiguration​ _serviceConfiguration ​=​
          ​AuthorizationServiceConfiguration​(
        _authorizeEndpoint,
        _tokenEndpoint,
      );
      _response ​=​ ​await​ _appAuth.​authorize​(
        ​AuthorizationRequest​(
          _clientId,
          _redirectUrl,
          serviceConfiguration​:​ _serviceConfiguration,
          scopes​:​ [
            ​"openid"​,
          ],
        ),
      );
    } ​on​ ​PlatformException​ ​catch​ (error) {
      ​print​(error.message);
      ​throw​ ​Exception​(​'Error logging out.  Could not log out.'​);
    }
    ​if​ (_response ​!=​ ​null​) {
      ​return​ _response;
    } ​else​ {
      ​return​ ​null​;
    }
  }

CHANGE "{TENANT}" to your tenant. This will even close the webview automatically afterwards.

Edit: Oof, just saw you're using iOS. Tested on Android only.

@hallidev
Copy link

@sharpsan Yeah your snippet (essentially) works for me on Android. The IdentityServer4 logout endpoint is called /endsession instead of /logout, but the idea is the same - abuse the Authorize call to logout. And this does work on Android, but I'd be surprised if your snippet worked on iOS.

That being said, it does appear that this is somehow working on iOS for @codenamics, though I can't guess why based on all my tests. My only guess is that somehow OKTA is sending valid auth response from its own endsession endpoint, though this makes no sense to me. Shrug

@dwhiteddsoft
Copy link

I have it working with both Android and iOS for Azure B2C https://www.detroitdave.dev/2020/04/simple-azure-b2c-flutter.html

@hallidev
Copy link

hallidev commented Apr 20, 2020

@dwhiteddsoft That's a clever use of using the login prompt value, but the actual logout process of getting dropped on the login page and then having to manually close the window wouldn't work for me UX-wise.

All of the conversations / blog posts / github issues I've seen so far lead me to believe that there is a gaping hole in AppAuth when it comes to logout. Even though every OAuth / OIDC provider I've seen provides a compliant endsession endpoint, the only suggestions I've seen regarding actually calling it require modifying the AppAuth library or writing plugins for it.

That or I'm missing something so obvious I should be embarrassed.

@MaikuB
Copy link
Owner

MaikuB commented Apr 20, 2020

The iOS AppAuth SDK supports it from what I know but the Android AppAuth SDK is missing it as it needs a new maintainer (see openid/AppAuth-Android#444). So even though there seems to be a PR for it in Android (https://github.com/openid/AppAuth-Android/pull/525/files), it won't be reviewed merged in unless there's a new maintainer.

One possible workaround is as I'm (currently) trying to depend on the official libraries, is to fork this plugin. Within that fork, update the Android dependencies to point to a fork of the Android AppAuth SDK that has logout support. If I'm not mistaken, this can be via JitPack. After that add the necessary code changes to support logout including calls to the iOS API that supposedly already exists

@hallidev
Copy link

hallidev commented Apr 20, 2020

@MaikuB It shouldn't have to be up to you to do that, although I'm sure there are some people (like myself a few days ago) who'd be thankful you did.

Android doesn't need it as bad as iOS does. If you read my comment above, the Authorize method can be easily and seamlessly abused to call the /endsession endpoint directly with no side effects. It works well.

That being said, I'm using @mgalsina's solution here and just dropping all of the tokens I'm storing locally. It has the added benefit of not showing the "such and such wants to Sign In using xxxxxx" dialog on iOS during sign out. Essentially the solution is to never allow the browser to store cookies in the first place via the loginPrompt and handle them all in LocalStorage in Flutter.

For me this works great as I don't plan on ever implementing federated sign out from my app, though now that I've written this, I'm sure it's destined to be a requirement within the next 30 days :)

@dwhiteddsoft
Copy link

I don't care for the UI experience either but I ran into the same thing both of you did. Since this lib uses AppAuth underneath, the delta of steps to get this all working is pretty steep. As I continue on in my Flutter journey this is a great example where the underlying libraries sometimes fail us and I wonder if more Flutter libraries should simply fork underlying libs into themselves and simply build an 'all in one' (allowing a mechanism to plug gaps if needed) instead of just a wrapper on top of a library.

@hallidev
Copy link

@dwhiteddsoft I highly suggest @mgalsina's solution as it would seem to solve the problem you put forth in your blog post and it has a great UX to boot.

For the bigger question of federated signout, it's really something that needs to be fixed in the core AppAuth libraries. Fixes for this don't belong in the Flutter libraries

@matthewtsmith
Copy link
Contributor

I've created a pr to help address the iOS side of this problem. With iOS 13 you can now pass prefersEphemeralWebBrowserSession which will prompt for login and not remember any cookies. It also has the added advantage of not showing the "Sign in using xxx" popup before showing you the web view.

There is no equivalent on Android. However as @hallidev mentions here there are easier ways to clear sessions on android including simply opening the LOGOUT endpoint and redirecting back to your application.

@pgulegin
Copy link

pgulegin commented Jun 3, 2020

Latest version works great for iOS!

Any updates on the Android side? If not, can we at least point to the Android AppAuth SDK that has logout support?

@MaikuB
Copy link
Owner

MaikuB commented Jun 4, 2020

As far as I know, Android doesn't support private auth sessions. If you want logout support on either platform, you can fork this repo and have it point to a fork of an AppAuth SDK that has support for logout. In case you haven't been following this thread, the official Android AppAuth SDK doesn't have support for logout as isn't an active maintainer for it

@MaikuB
Copy link
Owner

MaikuB commented Jul 22, 2020

Is there anyone who actually got a fork working where they implemented the end session functionality on iOS by using the API that's provide by the AppAuth iOS SDK? I tried playing around with it but it would show a prompt that the app wants to show the user to sign in and it disappears quickly too. I must be missing something but not sure what as there's a lack of examples

@MisterJimson
Copy link

@MaikuB I have only been able to do it with an HTTP request I send manually, instead of using the SDK.

@dukemike
Copy link

On iOS 13, I'm seeing the authentication session reset when using MFA which backgrounds the app when prefersEphemeralWebBrowserSession is true. Has anyone else noticed this?

@ameeee
Copy link

ameeee commented Oct 7, 2020

Anyone knows what's the difference between flutter_appauth and flutter msal_mobile, and whether it solves this issue?

@ameeee
Copy link

ameeee commented Oct 7, 2020

@mgalsina, do you store the refresh token somewhere in your case so that you can keep your user signed it? and is your solution secure (e.g.: Will the user remain signed in to b2c if we follow such method? I think he shouldn't)

@gabrimatic
Copy link

I solved the problem with a trick:

https://stackoverflow.com/a/65304425/9885611

@MaikuB
Copy link
Owner

MaikuB commented Dec 15, 2020

@gabrimatic is your app only targeting Android? Opening a browser on iOS led to issue for me #161

@MaikuB MaikuB added the help wanted Extra attention is needed label Jan 14, 2021
@jhoward321
Copy link

@MaikuB it looks like the Android PR that you mentioned earlier was merged with the end session support. Is this what was needed or is there something still missing?

@MaikuB
Copy link
Owner

MaikuB commented Mar 20, 2021

@jhoward321 I mentioned earlier in the thread that I couldn't get end session working on iOS. Note that I had put a "help wanted" label on this issue and this is open-source so you can fork the repo, try to get it working and submit a PR

@vicdotdevelop
Copy link

vicdotdevelop commented Mar 29, 2021

Hello @MaikuB,

I have managed to force my flutter iOS app to login as a new session everytime. Also got the revocation of the token running as my logout. Works well!

Now the problem: I am authenticating with my IDP via corporate user certificate. On Android the revocation and opening a new session etc works but it seems like the browser caches the certificate and proceeds the login. Even when the certificate is removed from the device the login still works. Is there any possibility to solve this issue.

iOS works like expected.
Android only when not cert auth is used.

for iOS I have used the prompt value login, added Origin Header with Redirect URL as value and the IDP had the possibility to add a CORS Domain. Added Redirect URI therefore there. Now it seems to work.

@ccadieux
Copy link

ccadieux commented May 4, 2021

@MaikuB I have this working on a fork for iOS. That said I had to work around the prompt. I needed to provide a SFSafariViewController Custom User-Agent. Seems to be the only way to make that work.

With ASWebAuthenticationSession I get the prompts at login and logout. With preferEphemeralSession endsession doesn't log me out.

Would you still be interested in a PR for this?

@MaikuB
Copy link
Owner

MaikuB commented Jun 27, 2021

@ccadieux I don't think a PR will be necessary. I figured out the issue on iOS so I've published a 2.0.0-dev.0 prerelease that adds support for end session requests. I've tried it out with the demo IdentityServer instance and with an Okta server. If the community could given this a spin that would great.

Edit: a prompt will appear will depending on the version of iOS used but this is expected behaviour given the APIs that are meant to be used

@ccadieux
Copy link

ccadieux commented Jul 5, 2021

My fork is using the deprecated SFSafariViewController because of that prompt. Prompting the user at sign out with a warning that they are going to sign in is not usable in my opinion. I tried 2.0.0-dev.0 and get the following prompt at logout.

Are you able to give us the option to use the deprecated SFSafariViewController. This restores the behaviour of no prompts. It's a weird situation but none of the new api's support what we need for this. I would make the default what you have and leave it up to the user to make the decision. With the warning that it may cause issues in the future.

You get this prompt at login and logout with 2.0.0-dev.0
Image from iOS

@MaikuB
Copy link
Owner

MaikuB commented Jul 10, 2021

@ccadieux would you submit be able to PR for this to the end_session branch?

@eatplaysleep
Copy link

Part of the challenge with this ask is that technically the OIDC spec doesn't support "logout". In my opinion, it's way overdue for being addressed. There are a couple of drafts out there but nothing (to my knowledge) that's been finalized.

OpenID Connect Session Management 1.0 is on it's 30th draft and is referenced in the OIDC core spec as an implementer's guide.

In addition, there are a few other draft proposals out there:

All of these provide some possible ways to handle it, but it's ultimately just way too provider specific to solve in a plugin like this.

Some providers provide an end_session_endpoint our logout_endpoint in their discovery doc, but it's not officially supported in the final spec.

So, why is this?

We need to explore some of the reasons OIDC exists to answer that...

OIDC is not intended handle 'sessions'

Or, more simply, You don't own the session.

One of the purposes of OIDC was to get away from the traditional AuthZ session concept so that native apps could more easily manage authentication. It's about separation of duties and, ultimately, federation. The Idp is responsible for their session and then each client is responsible for their "session".

If you really think about it, what y'all are talking about is wanting to be able to terminate the Idp's session, which, is kind of crazy!

I know, you are all thinking "this guy is nuts, what is he talking about?!" and that is because most of you are dealing with situations where the client/RP/Idp are all the same company so it becomes really easy to forget about the division that is "supposed" to exist there.

Let's talk about Facebook

To help explain this further, think about it in the context of using Facebook. If you are using a bare-bones, pure OIDC, implementation and only offer 'Login with Facebook' in your app (I've seen apps like this in the wild!) then what you're essentially proposing is having a button in your app that logs the user out from Facebook.

We all know FB would never allow that! They would say, "you can revoke access and kill your tokens, but you can't manage the FB session." And, that's precisely why there isn't really a 'logout' concept in OIDC -- you're supposed to be revoking access per client.

Ok, but that's stupid...

But is it? One of the perks of OIDC is the ability to consume a pre-existing 'session' and reduce the number of times someone has to login. Isn't it super convenient when you can click on 'Login' and not have to actually do anything? That's because the Idp 'session' has been abstracted and is pretty much what federation is all about.

Right, but, that doesn't really work in the real world...

Totally agree and this is why the idea of 'Single Logout' (SLO) is gaining momentum and also why logout continues to be incredibly provider specific and not standardized. However, most providers have APIs for session management and that's where I see SLO starting to head.

To me, it would make total sense to expose a 'SLO' endpoint that does NOT require opening a browser. In reality, the browser should be validating the 'session' held by the cookie anyway so, if you terminate the session via API, it should effectively kill the cookie the next time someone tries to use it. It could be exposed via the .well-known and, if an Idp doesn't expose it, you know it's not supported.

The big question then...

SO WHAT?
In the short term I think it makes sense for this plugin to provide a revoke capability as this is a standard OAuth function and supposed to be exposed via the .well-known. The AppAuth SDK exposes EndSessionRequest for both iOS and Android (which calls the /revoke endpoint). Perhaps that's a starting point for the Flutter plugin...?

@MaikuB
Copy link
Owner

MaikuB commented Aug 13, 2021

@eatplaysleep Perhaps you missed reading this post above? #48 (comment)

@eatplaysleep
Copy link

@eatplaysleep Perhaps you missed reading this post above? #48 (comment)

I saw it. I'm not a fan of the UX for that method though. Would still be in favor of a implementing revoke until a better mechanism is made available in AppAuth itself.

@MaikuB
Copy link
Owner

MaikuB commented Aug 13, 2021

Said UX is from the end session request that you mentioned

@MaikuB
Copy link
Owner

MaikuB commented Nov 9, 2021

@ccadieux did you ever end up submitting a PR? I vaguely remember seeing a branch added to the repository but don't know how it ended up there so I've deleted it. Don't remember if it was from you or not as it seemed strange that someone else could've push their own branch here so I didn't take much note of it

@MaikuB
Copy link
Owner

MaikuB commented Nov 10, 2021

FYI I've published 2.0.0-dev.6 that should fix an issue where choosing the cancel when prompted by the dialog that appears on an end session request would prevent subsequent requests from working. If I could get help from others here to confirm that it works and that I haven't broken other flows (as I've applied a similar code change to other places) that they would be much appreciated. I didn't find any issues in my own testing but be good to get more feedback on this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet