appsup-dart / openid_client

Library for working with OpenID Connect and implementing clients.
BSD 3-Clause "New" or "Revised" License
90 stars 118 forks source link

How to authenticate with keycloak through authorization code flow? #11

Closed ildoc closed 4 years ago

ildoc commented 4 years ago

I'm trying to authenticate my flutter app to keycloak

following the repo example, I've wrote an authentication function like this

authenticate() async {

  // parameters here just for the sake of the question
  var uri = Uri.parse('https://keycloak-url/auth/realms/myrealm');
  var clientId = 'my_client_id';
  var scopes = List<String>.of(['openid', 'profile']);
  var port = 4200;
  var redirectUri = Uri.parse('http://localhost:4200');

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

  urlLauncher(String url) async {
    if (await canLaunch(url)) {
      await launch(url, forceWebView: true);
    } else {
      throw 'Could not launch $url';
    }
  }

  var authenticator = new Authenticator(client,
      scopes: scopes,
      port: port,
      urlLancher: urlLauncher,
      redirectUri: redirectUri);

  var c = await authenticator.authorize();
  closeWebView();

  var token= await c.getTokenResponse();
  print(token);
  return token;
}

when I call the function, a webview popup appears and I can login through keycloak, but when the popup closes I get this error at the c.getTokenResponse():

Exception has occurred. NoSuchMethodError (NoSuchMethodError: The getter 'length' was called on null. Receiver: null Tried calling: length)

inspecting the Credential c, I can see that the TokenResponse has only "state", "session_state" and "code" fields

what am I missing?

rbellens commented 4 years ago

Hi @ildoc,

Do you have the stack trace?

ildoc commented 4 years ago

sure!

Object.noSuchMethod (dart:core-patch/object_patch.dart:53)
_Uri._uriEncode (dart:core-patch/uri_patch.dart:46)
Uri.encodeQueryComponent (dart:core/uri.dart:1105)
mapToQuery.<anonymous closure> (c:\src\flutter\.pub-cache\hosted\pub.dartlang.org\http-0.12.0+4\lib\src\utils.dart:19)
CastMap.forEach.<anonymous closure> (dart:_internal/cast.dart:288)
_LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:379)
CastMap.forEach (dart:_internal/cast.dart:287)
mapToQuery (c:\src\flutter\.pub-cache\hosted\pub.dartlang.org\http-0.12.0+4\lib\src\utils.dart:17)
Request.bodyFields= (c:\src\flutter\.pub-cache\hosted\pub.dartlang.org\http-0.12.0+4\lib\src\request.dart:137)
BaseClient._sendUnstreamed (c:\src\flutter\.pub-cache\hosted\pub.dartlang.org\http-0.12.0+4\lib\src\base_client.dart:170)
BaseClient.post (c:\src\flutter\.pub-cache\hosted\pub.dartlang.org\http-0.12.0+4\lib\src\base_client.dart:58)
post.<anonymous closure> (c:\src\flutter\.pub-cache\hosted\pub.dartlang.org\openid_client-0.2.5\lib\src\http_util.dart:23)
_withClient (c:\src\flutter\.pub-cache\hosted\pub.dartlang.org\openid_client-0.2.5\lib\src\http_util.dart:36)
post (c:\src\flutter\.pub-cache\hosted\pub.dartlang.org\openid_client-0.2.5\lib\src\http_util.dart:22)
Credential.getTokenResponse (c:\src\flutter\.pub-cache\hosted\pub.dartlang.org\openid_client-0.2.5\lib\src\openid.dart:219)
_MyAppState.authenticate (c:\my_app\lib\my_app.dart:94)
_rootRunUnary (dart:async/zone.dart:1134)
_rootRunUnary (dart:async/zone.dart:0)
_CustomZone.runUnary (dart:async/zone.dart:1031)
_FutureListener.handleValue (dart:async/future_impl.dart:140)
rbellens commented 4 years ago

On mobile devices you should use the PKCE flow. This is automatically selected when you omit the redirect uri in the Authenticator constructor.

So, it should be:

var authenticator = new Authenticator(client,
      scopes: scopes,
      port: port,
      urlLancher: urlLauncher,);

Make sure you add the uri http://localhost:4200/ (including the trailing slash) to Valid Redirect URIs in keycloak.

image

ildoc commented 4 years ago

thank you! it worked :D

vinodlee commented 4 years ago

thank you! it worked :D

Hi @ildoc can you please share the example code, I have tried many packages but am not done keycloak authentication.

nicmaster commented 4 years ago

I would also appreciate if someone share the code

ildoc commented 4 years ago

this is our implementation with providers

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:openid_client/openid_client_io.dart' as oidc;
import 'package:url_launcher/url_launcher.dart';

import '../core/config/secure_storage.dart';
import '../core/http/http_functions.dart';
import '../models/index.dart' as models;

class Auth with ChangeNotifier {
  Auth() {
      authenticate();
  }

  models.UserInfo _userInfo;

  bool _isAuth = false;
  bool get isAuth {
    if (_userInfo == null) {
      authenticate();
      _isAuth = false;
      return false;
    }
    _isAuth = true;
    return true;
  }

  models.UserInfo get user => _userInfo;

  _urlLauncher(String url) async {
    if (await canLaunch(url)) {
      await launch(url, forceWebView: true);
    } else {
      throw 'Could not launch $url';
    }
  }

  Future<void> authenticate() async {
    if (_connectivity.isInit == false) return;
    //get values from securestorage and create a client
    var _storage = SecureStorage();
    var authorizationEndPoint =
        await _storage.getStoredValue('authorizationEndPoint');
    var clientId = await _storage.getStoredValue('clientId');
    var redirectUriPort = await _storage.getStoredValue('redirectUriPort');

    oidc.Credential credential;
    bool refreshFail = false;
    bool accessTokenSaved = await _storage.getAccessToken() != null;
    //if there is an access token saved in the secure storage try to get a new token using the refresh token
    if (accessTokenSaved) {
      print("login using saved token");
      final tt = await _storage.getTokenType();
      final rt = await _storage.getRefreshToken();
      final it = await _storage.getIdToken();

      var issuer = await oidc.Issuer.discover(Uri.parse(authorizationEndPoint));
      var client = new oidc.Client(issuer, clientId);
      credential = client.createCredential(
        accessToken: null, // force use refresh to get new token
        tokenType: tt,
        refreshToken: rt,
        idToken: it,
      );

      credential.validateToken(validateClaims: true, validateExpiry: true);
      try {
        _saveToken(await credential.getTokenResponse());
      } catch (e) {
        print("Error during login (refresh) " + e.toString());
        refreshFail = true;
      }
    }
    if (!accessTokenSaved || refreshFail) {
      var issuer = await oidc.Issuer.discover(Uri.parse(authorizationEndPoint));
      var client = new oidc.Client(issuer, clientId);
      //auth from browser
      var authenticator = oidc.Authenticator(
        client,
        scopes: List<String>.of(['openid', 'profile', 'offline_access']),
        port: int.parse(redirectUriPort),
        urlLancher: _urlLauncher,
      );
      credential = await authenticator.authorize();
      closeWebView();
      //save Token
      _saveToken(await credential.getTokenResponse());
    }

    customGetTokenResponse() async {
      var token = await credential.getTokenResponse();
      print("called getTokenResponse, token expiration:" +
          token.expiresAt.toIso8601String());

      await _saveToken(token);
      return token;
    }

    //call getTokenResponseFn before each http request to get a new token if old one is expired
    var http = HttpFunctions()..getTokenResponseFn = customGetTokenResponse;

    final response = await http.get('api/users/UserInfo'); // this call is authenticated
    _userInfo = models.UserInfo.fromJson(response);
    _userInfo =
        _userInfo.copyWith(id: response['userId'], mail: response['email']);
    notifyListeners();
  }

  Future _saveToken(oidc.TokenResponse token) async {
    SecureStorage().saveToken(
        accessToken: token.accessToken,
        refresToken: token.refreshToken,
        tokenType: token.tokenType,
        idToken: token.idToken.toCompactSerialization());
  }

  void logout() {
    _userInfo = null;
    SecureStorage().clearToken().then((_) => notifyListeners());
  }
}
FrenchTastic commented 4 years ago

Thanks @ildoc for your code and @rbellens for your answer and your work ! But that does not answer to How to make it work with Authorization Flow on Flutter

I have an "organization" issue, I have to work with Keycloak v4. It does not support PKCE Flow. So I have the choice of using Implicit Flow (which I already use on Native mobile app) or Authorization Flow Code.

You said that it's not possible for now ?

I think with Implicit Flow I can make it work without this lib, with a webview, but it's not ideal.

Thanks guys !

ldemyanenko commented 4 years ago

this is our implementation with providers

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:openid_client/openid_client_io.dart' as oidc;
import 'package:url_launcher/url_launcher.dart';

import '../core/config/secure_storage.dart';
import '../core/http/http_functions.dart';
import '../models/index.dart' as models;

class Auth with ChangeNotifier {
  Auth() {
      authenticate();
  }

  models.UserInfo _userInfo;

  bool _isAuth = false;
  bool get isAuth {
    if (_userInfo == null) {
      authenticate();
      _isAuth = false;
      return false;
    }
    _isAuth = true;
    return true;
  }

  models.UserInfo get user => _userInfo;

  _urlLauncher(String url) async {
    if (await canLaunch(url)) {
      await launch(url, forceWebView: true);
    } else {
      throw 'Could not launch $url';
    }
  }

  Future<void> authenticate() async {
    if (_connectivity.isInit == false) return;
    //get values from securestorage and create a client
    var _storage = SecureStorage();
    var authorizationEndPoint =
        await _storage.getStoredValue('authorizationEndPoint');
    var clientId = await _storage.getStoredValue('clientId');
    var redirectUriPort = await _storage.getStoredValue('redirectUriPort');

    oidc.Credential credential;
    bool refreshFail = false;
    bool accessTokenSaved = await _storage.getAccessToken() != null;
    //if there is an access token saved in the secure storage try to get a new token using the refresh token
    if (accessTokenSaved) {
      print("login using saved token");
      final tt = await _storage.getTokenType();
      final rt = await _storage.getRefreshToken();
      final it = await _storage.getIdToken();

      var issuer = await oidc.Issuer.discover(Uri.parse(authorizationEndPoint));
      var client = new oidc.Client(issuer, clientId);
      credential = client.createCredential(
        accessToken: null, // force use refresh to get new token
        tokenType: tt,
        refreshToken: rt,
        idToken: it,
      );

      credential.validateToken(validateClaims: true, validateExpiry: true);
      try {
        _saveToken(await credential.getTokenResponse());
      } catch (e) {
        print("Error during login (refresh) " + e.toString());
        refreshFail = true;
      }
    }
    if (!accessTokenSaved || refreshFail) {
      var issuer = await oidc.Issuer.discover(Uri.parse(authorizationEndPoint));
      var client = new oidc.Client(issuer, clientId);
      //auth from browser
      var authenticator = oidc.Authenticator(
        client,
        scopes: List<String>.of(['openid', 'profile', 'offline_access']),
        port: int.parse(redirectUriPort),
        urlLancher: _urlLauncher,
      );
      credential = await authenticator.authorize();
      closeWebView();
      //save Token
      _saveToken(await credential.getTokenResponse());
    }

    customGetTokenResponse() async {
      var token = await credential.getTokenResponse();
      print("called getTokenResponse, token expiration:" +
          token.expiresAt.toIso8601String());

      await _saveToken(token);
      return token;
    }

    //call getTokenResponseFn before each http request to get a new token if old one is expired
    var http = HttpFunctions()..getTokenResponseFn = customGetTokenResponse;

    final response = await http.get('api/users/UserInfo'); // this call is authenticated
    _userInfo = models.UserInfo.fromJson(response);
    _userInfo =
        _userInfo.copyWith(id: response['userId'], mail: response['email']);
    notifyListeners();
  }

  Future _saveToken(oidc.TokenResponse token) async {
    SecureStorage().saveToken(
        accessToken: token.accessToken,
        refresToken: token.refreshToken,
        tokenType: token.tokenType,
        idToken: token.idToken.toCompactSerialization());
  }

  void logout() {
    _userInfo = null;
    SecureStorage().clearToken().then((_) => notifyListeners());
  }
}

Looks like logout isn't working correctly in this implementation. If try to authenticate two times in a row, I'm not able to change the user.

MohabAbugabal commented 3 years ago

Hello guys. Frustrated by this so much that I decided to ask you. Can anyone share the below files? Or the full code required to do the connection. Will be highly appreciated. import '../core/config/secure_storage.dart'; import '../core/http/http_functions.dart'; import '../models/index.dart' as models;

dawnseeker8 commented 3 years ago

will share something tomorrow, hang in there @FrankDupree Hello, Can you shared those files please? import '../core/config/secure_storage.dart'; import '../core/http/http_functions.dart'; import '../models/index.dart' as models;

currently i also try to make this working with flutter but without luck. your help will be very appreciated.

talbiislam96 commented 3 years ago

Hi , i followed your example in my app to authenticate with keycloak , however it is not working , it doesn't redirect me to keycloak , this is the function am using (following this example) :

 authenticate() async {
    var uri = Uri.parse('http://10.0.2.2:8080/auth/realms/clients');
    var clientId = 'helium';
    var scopes = List<String>.of(['openid', 'profile']);
    var port = 4200;
    var redirectUri = Uri.parse('http://localhost:4200');

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

    urlLauncher(String url) async {
      if (await canLaunch(url)) {
        await launch(url, forceWebView: true);
      } else {
        throw 'Could not launch $url';
      }
    }

    var authenticator = new Authenticator(client,
      scopes: scopes,
      port: port,
      urlLancher: urlLauncher,);

    var c = await authenticator.authorize();
    closeWebView();

    var token= await c.getTokenResponse();
    print(token);
    return token;
  }

Am calling it inside a login button:


   RoundedButton(
              text: "Login",
                press: (){

                authenticate();
                }

                //Navigator.push(
                  //context,
                 //MaterialPageRoute(
                  // builder: (context) {
                     //return MainMenu();
                    //},
                 // ),
               //);

                 ,
            ),

And this is the error i keep getting whenever i hit the login button :


E/flutter ( 5769): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: Instance of 'HttpRequestException'
E/flutter ( 5769): #0      _processResponse (package:openid_client/src/http_util.dart:37:5)
E/flutter ( 5769): #1      get (package:openid_client/src/http_util.dart:18:10)
E/flutter ( 5769): <asynchronous suspension>
E/flutter ( 5769): #2      Issuer.discover (package:openid_client/src/openid.dart:124:16)
E/flutter ( 5769): <asynchronous suspension>
E/flutter ( 5769): #3      Body.authenticate (package:helium_app/screens/login/components/body.dart:35:18)
E/flutter ( 5769): <asynchronous suspension>

Am i missing something ? am kind of confused , beacause in the implimentation you shared there is a nother configuration in the code besides the function , where should i add it and what is it used for ? Is there anything else i need to add to the function (keycloak is perfetly configured) ? been stuck for a while because of this issue , so if anyone knows how to fix this i'd be so grateful .

funder7 commented 3 years ago

Hi , i followed your example in my app to authenticate with keycloak , however it is not working , it doesn't redirect me to keycloak , this is the function am using (following this example) :


 authenticate() async {
    var uri = Uri.parse('http://10.0.2.2:8080/auth/realms/clients');
    var clientId = 'helium';
    var scopes = List<String>.of(['openid', 'profile']);
    var port = 4200;
    var redirectUri = Uri.parse('http://localhost:4200');

Hello @talbiislam96, I think that you're calling the wrong endpoint for authentication, mine is : http://localhost:9080/auth/realms/{{realm-name}}/protocol/openid-connect/auth

This is for autentication. If you need get a new auth token when it expires, by refreshing it, then /authmust be replaced with /token.

There's an useful endpoint which you can call to get your realm configuration:

http://localhost:9080/auth/realms/{{realm-name}}/.well-known/openid-configuration

It will give you many useful information, especially regarding which endpoints are available on the IdP for you:

{
    "issuer": "http://localhost:9080/auth/realms/jhipster",
    "authorization_endpoint": "http://localhost:9080/auth/realms/jhipster/protocol/openid-connect/auth",
    "token_endpoint": "http://localhost:9080/auth/realms/jhipster/protocol/openid-connect/token",
    "introspection_endpoint": "http://localhost:9080/auth/realms/jhipster/protocol/openid-connect/token/introspect",
    "userinfo_endpoint": "http://localhost:9080/auth/realms/jhipster/protocol/openid-connect/userinfo",
    "end_session_endpoint": "http://localhost:9080/auth/realms/jhipster/protocol/openid-connect/logout",
    "jwks_uri": "http://localhost:9080/auth/realms/jhipster/protocol/openid-connect/certs",
    //...
}

(my realm is named 'jhipster')

This endpoint can be used by some plugins to discover the identity provider configuration automatically. It will determine which endpoints to call to get a new auth token, or refresh an existing one. I wasn't able to use this feature, so I'm configuring the endpoints manually... It may be useful when configurations may change, but if you have a single one, then doing the setup manually it's still a valid solution.

A little note: I always had problems without HTTPS

Hope that this may help you, cheers!

hedi-ghodhbane commented 3 years ago

Hello guys. Can anyone share the below files? Or the full code required to do the connection. Will be highly appreciated. import '../core/config/secure_storage.dart'; import '../core/http/http_functions.dart'; import '../models/index.dart' as models

artoniaz commented 3 years ago

Hey, I'm also having difficulties with this issue. Does anybody have any full working example of Keycloak authentication in flutter? I was looking for any example projcets, tutorial etc. but with no luck. Cheers :)

talbiislam96 commented 3 years ago

@funder7 thank you so much for ur feedback . This is the function am using , it works perfectly fine it redirects to Keycloak and allows the users to authenticate , I wish to refresh the token am retrieving automatically to get a new one whenever it expires to keep the user session active if anyone knows how to do it I'd be grateful


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);

    }
funder7 commented 3 years ago

@talbiislam96 in order to refresh: 1) Once you login, and obtain the access token, save it securely with something like secure_storage, you can find the package on pub.dev, store the token as is. Other than the access token, you get the refresh token once login is done. You can use it later to obtain a fresh access token, when the one you posses is expired (see point #3) 2) When you need to perform any http call, get the access token you saved, from secure_storage (using the same key used for saving it). Once you have the access token, you can refresh it directly without any check, or instead check if it's still valid (not expired). To do that, you can use this function:

  bool _isTokenExpired(String accessToken) {
    Map<String, dynamic> accessItems = _parseAccessToken(accessToken);
    DateTime expiration =
        DateTime.fromMillisecondsSinceEpoch(accessItems['exp'] * 1000);
    bool isExpired = DateTime.now().isAfter(expiration);

    if (isExpired) {
      debugPrint('Token expired (at: $expiration)');
    }

    return isExpired;
  }

It checks the token's "exp" param, which contains the token expiration datetime expressed in milliseconds. You compare it with current time, then if exp is before current time, it's expired.

3) In case it's expired, or anyway, to refresh the access token, you must call the refreshendpoint. Calling the refresh endpoint by itself it's not enough to obtain a new access token: you must call the refresh endpoint with the refresh token in your possess (which you must have saved before in secure_storage, same as you did for access token).

To recap: 1) Once you login, the identity provider returns you: access, identity & refresh tokens 2) Access token is what you can use to access secured resources (eg. an http endpoint which needs authentication) 3) Refresh token is what you must use when the access token is expired. 4) Before doing any http call, the current access_token expiration time must be checked. If it is after current time, token is expired: is no more valid, and must be refreshed by calling the refresh endpoint. 5) Once you call the refresh endpoint, with a valid refresh token, the identity provider will answer with a new access token, and a new refresh token: store them again in secure storage, by overriding the previous ones.

I cannot provide you the code for calling the refresh endpoint as I'm using another library now (flutter_appauth), but the process doesn't change.

good luck!

EgomL commented 2 years ago

Hello @ildoc, Can anyone share the below files? Or the full code required to do the connection. Will be highly appreciated. import '../core/config/secure_storage.dart'; import '../core/http/http_functions.dart'; import '../models/index.dart' as models

ANMichelini commented 2 years ago

Hello everyone, im trying to refresh the access token but i cant find the way with this library. please, could someone just share the code of the function that does this? @ildoc your example really helped me, if you have that function please show it to me. thanks

MouhamedMokkhtar commented 2 years ago

Hello @ildoc, Can anyone share the below files? Or the full code required to do the connection. Will be highly appreciated. import '../core/config/secure_storage.dart'; import '../core/http/http_functions.dart'; import '../models/index.dart' as models

Adesanya-David commented 1 year ago
Future<TokenResponse> authenticate(Uri uri, String clientId,
      List<String> scopes, BuildContext context) async {
    try {
      var issuer = await Issuer.discover(uri);
      var client = Client(issuer, clientId);

      urlLauncher(String url) async {
        Uri uri = Uri.parse(url);
        if (await launchUrl(uri)) {
          await launchUrl(
            uri,
          );
        } else {
          throw 'Could not launch $url';
        }
      }

      var authenticator = Authenticator(
        client,
        scopes: scopes,
        urlLancher: urlLauncher,
        port: 3000,
      );

      var c = await authenticator.authorize();

      closeInAppWebView();
      var res = await c.getTokenResponse();
      UserInfo use = await c.getUserInfo();
      authUserId(use.subject.toString());
      email(use.email.toString());
      token(res.accessToken.toString());

      logoutUrl = c.generateLogoutUrl();   //This would be needed for logout
      return res;
    } finally {
      context.loaderOverlay.hide();
    }
  }

  Future<void> logout() async {
    if (!await canLaunchUrl(logoutUrl) || await canLaunchUrl(logoutUrl))  //I used both because one works on some devices and the other doesn't
    {
      await launchUrl(logoutUrl);
      token('');
      Get.offAll(() => const LogOn());
    } else {
      throw 'Could not launch $logoutUrl';
    }
    await Future.delayed(const Duration(seconds: 2));
    closeInAppWebView();
  }

I use openId_client package in a flutter project for keycloak, url_launcher-6.1.6 to launch urls. Also remember to setup keycloak in the admin console before proceeding with this.

Also please note that there are some custom pages or functions. You can try creating them in your file after you paste this code.

You'll also need to parse your issuer(a link) and clientId in a .env file.

I hope this helps

damitervin commented 1 year ago
Future<TokenResponse> authenticate(Uri uri, String clientId,
      List<String> scopes, BuildContext context) async {
    try {
      var issuer = await Issuer.discover(uri);
      var client = Client(issuer, clientId);

      urlLauncher(String url) async {
        Uri uri = Uri.parse(url);
        if (await launchUrl(uri)) {
          await launchUrl(
            uri,
          );
        } else {
          throw 'Could not launch $url';
        }
      }

      var authenticator = Authenticator(
        client,
        scopes: scopes,
        urlLancher: urlLauncher,
        port: 3000,
      );

      var c = await authenticator.authorize();

      closeInAppWebView();
      var res = await c.getTokenResponse();
      UserInfo use = await c.getUserInfo();
      authUserId(use.subject.toString());
      email(use.email.toString());
      token(res.accessToken.toString());

      logoutUrl = c.generateLogoutUrl();   //This would be needed for logout
      return res;
    } finally {
      context.loaderOverlay.hide();
    }
  }

  Future<void> logout() async {
    if (!await canLaunchUrl(logoutUrl) || await canLaunchUrl(logoutUrl))  //I used both because one works on some devices and the other doesn't
    {
      await launchUrl(logoutUrl);
      token('');
      Get.offAll(() => const LogOn());
    } else {
      throw 'Could not launch $logoutUrl';
    }
    await Future.delayed(const Duration(seconds: 2));
    closeInAppWebView();
  }

I use openId_client package in a flutter project for keycloak, url_launcher-6.1.6 to launch urls. Also remember to setup keycloak in the admin console before proceeding with this.

Also please note that there are some custom pages or functions. You can try creating them in your file after you paste this code.

You'll also need to parse your issuer(a link) and clientId in a .env file.

I hope this helps

Hi there, can I ask some?

Do you use it for mobile or web app? Do you import these files only?

import 'dart:async'; import 'package:flutter/material.dart'; import 'package:openid_client/openid_client_io.dart'; import 'package:url_launcher/url_launcher.dart';

Where shall I put the code (unfortunately, I am quite amateur with Flutter : 6) E.g. in Scaffold?, after @override, etc.

Here is my sample:

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:openid_client/openid_client_io.dart';
import 'package:url_launcher/url_launcher.dart';

class Login extends StatefulWidget {
  const Login({Key? key}) : super(key: key);

  @override
  _LoginState createState() => _LoginState();
}

class _LoginState extends State<Login> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[300],
      body: Padding(
        padding: EdgeInsets.only(left:20.0, top:40.0,right:20.0,bottom:10.0),
        child:
          Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.start,
              children: [
                SizedBox(height: 50)
                Text(
                  'Hello',
                  style: TextStyle(
                  fontWeight: FontWeight.bold,
                  fontSize: 24,
                  ),
                ),
                SizedBox(height: 10),
                Text('Welcome, you have been missed.',
                style: TextStyle(
                fontWeight: FontWeight.bold,
                fontSize: 20,
                ),
                ),
                SizedBox(height: 30),
                // email tfield
                Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 25.0),
                  child: Container(
                    decoration: BoxDecoration(
                      color: Colors.grey[200],
                      border: Border.all(color: Colors.white),
                      borderRadius: BorderRadius.circular(20)
                    ),
                      child: Padding(
                        padding: const EdgeInsets.only(left: 20.0),
                        child: TextField(
                          decoration: InputDecoration(
                            border: InputBorder.none,
                            hintText: 'Email'
                          ),
                        ),
                      )
                  ),
                ),
                SizedBox(height: 10),
                // password textfield
                Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 25.0),
                  child: Container(
                      decoration: BoxDecoration(
                          color: Colors.grey[200],
                          border: Border.all(color: Colors.white),
                          borderRadius: BorderRadius.circular(20)
                      ),
                      child: Padding(
                        padding: const EdgeInsets.only(left: 20.0),
                        child: TextField(
                          obscureText: true,
                          decoration: InputDecoration(
                              border: InputBorder.none,
                              hintText: 'Password'
                          ),
                        ),
                      )
                  ),
                ),

                // sign in button
            ]
            ),
          ),
      ),
    );
  }
}
Adesanya-David commented 1 year ago

I use for mobile. We can get on a call. I got your email already