openid_client icon indicating copy to clipboard operation
openid_client copied to clipboard

Refresh Token automatically after it expires to keep the user session active

Open talbiislam96 opened this issue 4 years ago • 8 comments

Hello everyone , am using the openeiclient package in my flutter app to redirect my users to Keycloak so they ken log in . My method works perfectly fine and am able to retrieve the JWT token , I want to be able to refresh the token am getting whenever it expires so I can keep my user session active and I have no idea how to do it .This is the function am using to redirect me to Keycloak :

authenticate() async {
      // keyclock url : key-clock-url : example : http://localhost:8080
      // my realm : name of your real.m
      var uri = Uri.parse('http://169.254.105.22:8080/auth/realms/Clients');
      // your client id
      var clientId = 'helium';
      var scopes = List<String>.of(['openid', 'profile']);
      var port = 8080;
      var issuer = await Issuer.discover(uri);
      var client = new Client(issuer, clientId);
      print(issuer.metadata);
      urlLauncher(String url) async {
        if (await canLaunch(url)) {
          await launch(url, forceWebView: true);
        } else {
          throw 'Could not launch $url';
        }
      }
      authenticator = new Authenticator(
        client,
        scopes: scopes,
        port: port,
        urlLancher: urlLauncher,
      );
      var c = await authenticator.authorize();
      closeWebView();
      var token = await c.getTokenResponse();
      var userInformation = await c.getUserInfo();
      setState(() {
        userAccessToken = token.accessToken;
        userRefreshToken = token.refreshToken;
        print (userRefreshToken);
        userName = userInformation.preferredUsername;
      });
      //print(token);
      //return token;
      parseJwt(userAccessToken);


    }

If you know how I can check if my token has expired then automatically ask for a new token (which include new accessToken and a new refreshToken ) please guide me through this for I've been stuck for a while .Thank u in advance

talbiislam96 avatar Feb 17 '21 09:02 talbiislam96

I tried as a normal http request I think it might work try this "https://YOUR_URL/auth/realms/REALNAME/protocol/openid-connect/token".

you need to send the data in x-www-form-urlencoded

body:

client_id: yourClientID grant_type: "refresh_token" refresh_token: YOURTOKEN client_secret: YOURSECRET

have you found a way for a logout ? revoke function is not working for me it sends an exception

edx-mohamed-khamis avatar Feb 17 '21 11:02 edx-mohamed-khamis

@khamisEDX Thank you for your feedback , can u please share with me the whole code am a bit confused as to where I should put the http request and what x-www-form-urlencoded means please if you have a full example can u share it with me ? as for the logout function am still not implementing one

talbiislam96 avatar Feb 17 '21 13:02 talbiislam96

@talbiislam96 Annotation 2021-02-17 165224

edx-mohamed-khamis avatar Feb 17 '21 13:02 edx-mohamed-khamis

Thank u , what am looking for though is a function in flutter that can do this

talbiislam96 avatar Feb 17 '21 14:02 talbiislam96

@talbiislam96 you can still do it through an http request post method will do ?

edx-mohamed-khamis avatar Feb 18 '21 04:02 edx-mohamed-khamis

I know but I want to be able to do it automatically whenever it expires

talbiislam96 avatar Feb 18 '21 10:02 talbiislam96

@khamisEDX I don't think we can use client secret in flutter mobile app it's not safe at all

hemeda3 avatar Mar 20 '22 06:03 hemeda3

var token = await c.getTokenResponse(); automatically refreshes the token if it's expired when its called. If you want a new one call it again. You can also use c.createHttpClient() and using that as your Client for your network request will automatically refresh the token too.

drkdelaney avatar Dec 08 '22 21:12 drkdelaney

This package currently does not support persisting state or proactively refresh the token. It is up to the user of this package to implement this functionality. It should be quite straightforward with the getTokenResponse and other existing functionalities.

If you believe, this functionality should be part of this package, feel free to create a PR.

rbellens avatar Dec 31 '22 15:12 rbellens

I Implemented it like below. Eventually Auth.login() gets called by a button in the application.

main.dart: Place where credential is stored for lifetime of application

var cred;

Future main() async {
  runApp(MyApp());
}

Authentication Service: Sets the credential

class Auth {
  static const String clientId = 'my-client-id';
  static const List<String> scopes = ['profile offline_access'];

  Future login(BuildContext context) async {
    Uri uri = Uri.parse("http://my-auth-service-url");

    var issuer = await Issuer.discover(uri);
    var client = Client(issuer, clientId);

    // create a function to open a browser with an url
    urlLauncher(String uri) async {
      var u = Uri.parse(uri);
      if (await canLaunchUrl(u)) {
        await launchUrl(u);
      } else {
        throw 'Could not launch $uri';
      }
    }

    // create an authenticator
    var authenticator = Authenticator(client,
        scopes: scopes, port: 4000, urlLancher: urlLauncher);

    // starts the authentication
    var credential = await authenticator.authorize();
    main.cred = credential;

    // close the webview when finished
    closeInAppWebView();

.... 
rest omitted for brevity
....

    Navigator.pushReplacement(context,
        MaterialPageRoute(builder: (context) => const Home(title: "")));
  }

Authentication Interceptor: Uses the credential to get token and refresh when necessary

class AuthenticationInterceptor implements RequestInterceptor {
  @override
  FutureOr<Request> onRequest(Request request) async {
    var credential = main.cred as Credential;
    var token = (await credential.getTokenResponse()).accessToken;

    try {
      request.headers["Authorization"] = 'Bearer $token';
      request.headers["Accept"] = 'application/json';
      request.headers["content-type"] = 'application/json';
    } catch (e) {
      // TODO: Handle Error here
    }
    return request;
  }
}

slomangino123 avatar May 11 '23 13:05 slomangino123