Flutter mobile application to consume your WSO2 Cloud APIs with PKCE

In this article, we are discussing on implementing a Flutter mobile application which securely invokes an API through WSO2 API Cloud using “Authorization code grant with proof key for code exchange (PKCE)”.
We have discussed about PKCE flow and why it has been introduced to address security threats of public clients such as mobile or single page applications in our previous article.
Still haven’t read our previous article? Have a look at “Securely Consume your WSO2 Cloud APIs from Mobile/Single-Page Applications” to catch up background and generic security mechanism of PKCE.
Let’s begin!
Choosing a cross platform mobile application framework
With our previous article we have already discussed choosing recommended security standards, choosing API Management SaaS, etc. Here let’s quickly have a look on choosing our mobile application framework.
Companies build mobile apps on both iOS and Android and thus it helps businesses to target customers across the world. But developing specific native apps increases cost and consumes time. The “write once, run anywhere” approach that comes with cross platform applications allows developers to utilize a single code on multiple platforms, which greatly reduces costs and shortens the development time , unlike native apps. Therefore world is moving towards cross platform mobile applications that can address iOS and Android both aspects.

When it comes to choosing a cross platform framework, Flutter and React Native frameworks are the leading contenders while React Native is more matured and Flutter is better in performance with more support for native components. Experts have predicted that Flutter will be the future of mobile application development. Furthermore Flutter has an ‘AppAuth’ library named ‘flutter_appauth’ which handles the ‘authorization code with PKCE’ flow. Considering those reasons and after comparing both frameworks, we decided to use Flutter to develop this sample mobile application.
However still there are pros and cons of all of these frameworks and you need to decide which one to pick according to your requirements. Please refer this article for more details on Flutter vs React Native vs Native Comparison.
Now let’s setup the development environment and start implementing a mobile application to consume your WSO2 API Cloud APIs with PKCE.
Setup your Environment
Development Environment, one of:
These IDEs integrate well with Flutter. You will need an installation of the Dart and Flutter plugins, regardless of the IDE you decide to use.
Next download and install Flutter SDK and set path in your .bashrc
file and source it.
Project Setup
- Clone GitHub sample project —
git clone https://github.com/erandiganepola/wso2-cloud-flutter-demo.git
- In this project you will use three main dependencies. Those are
http
,flutter_appauth
andflutter_secure_storage
. Refer official documentations of http, flutter_appauth and flutter_secure_storage for more information. - Install dependencies by clicking
Pub get
in your IDE or run the following command in the project root:
flutter pub get
Prerequisites to setup in WSO2 Cloud
Note:
We have already discussed most of the prerequisites steps in our previous article. But here we have enabled “Allow authentication without client secret configuration” under the OIDC service provider config in WSO2 API Cloud to use Authorization code grant type with PKCE without client secret (You can enable it for your tenant application by requesting it via an email. Drop an email tocloud@wso2.com
mentioning your tenant domain and client application name.).Therefore we are not passing client secret in our code level requests. If you have not enabled this, you need to pass “client secret” in your code level requests.
- Create an account in WSO2 Cloud if you don’t have one already and login to Publisher portal.
- Create an API using an existing swagger definition. You can find the relevant swagger in swagger.yaml file. When creating your API, set context as
/demo
and set the production, sandbox backend endpoints tohttps://restcountries.eu/rest/v2
. Also selectSubscription Tiers
inManage
tab as necessary (I have set toUnlimited
in my API). Then publish your API. At the end of this demo your API will be calling REST Countries Capital City endpoint (https://restcountries.eu/#api-endpoints-capital-city) as the backend. - Visit WSO2 API Store and create an application. Enable (put a tick for the
Code
grant box) code grant with the callback URI. We have set callback URI toorg.wso2.cloud.flutterdemo://login-callback
in this sample. Then generate keys. Client ID/Consumer key, client secret/Consumer secret will be useful in our next steps.

- Subscribe to previously published API from the newly created application.
- You need to enable “Allow authentication without client secret configuration” under the OIDC service provider config in WSO2 API Cloud to use
Authorization code grant type with PKCE
without client secret. For that please send an email tocloud@wso2.com
to configure it for your application. Instead, you can pass the “client_secret” in requests as well. - Set relevant values as your
AUTH_CLIENT_ID
andTENANT_DOMAIN
in cloned project'slib/utils/constants.dart
file. Sample is given below:
/// Global Constants// WSO2 API Cloud URL domain
const String AUTH_DOMAIN = 'gateway.api.cloud.wso2.com';// Your client ID obtained by creating an application in WSO2 Store
const String AUTH_CLIENT_ID = 'fO0rk7lzuWZKRofN13zxxxxxxx';// Call back URL specified in your application
const String AUTH_REDIRECT_URI = 'org.wso2.cloud.flutterdemo://login-callback';// Auth token issuer domain
const String AUTH_ISSUER = 'https://$AUTH_DOMAIN';// Your tenant domain
const String TENANT_DOMAIN = 'erandiorg';
Run the Application
To run the application you have two options. Either to run in your mobile application or to run in a simulator.
- Option 01 : Enable developer options and USB Debugging in mobile phone’s settings and run the application after connecting your mobile phone to your development environment.
- Option 02 : Launch either the iOS simulator or Android emulators, then run the application.
You can run the application from your IDE or using following command:
flutter run -d all
Login, Invoke API, Change Settings and Logout
- After running the application, a UI will be popped out with a button named
Login to Cloud
.

2. Once user clicks Login to Cloud
button, s/he will be navigated to WSO2 authorization login page. There user needs to enter username as youruser@email.com@<tenantdomain>
and give password.

3. Then approve access to user profile information.

4. When it’s successful, user will be navigated to Home page. In the Home page you can enter a capital of a country and click search icon
in the right side of the search box. You will see results in the UI.


5. If you need to try out the sample for different tenant domains and different client applications rather than the one you configured in the constant.dart
file, you can change those configurations by clicking settings icon
in the top bar. Then a dialog box will be popped up with the existing values for client ID and tenant domain. You can edit them and click Update
to save.

6. If user needs to sign out, click power icon
in the top bar right side corner. Then confirmation box will be popped up. When you click Yes
it will log out user from the application.

Implementation
To understand the implementation, first let’s identify the flow of this scenario. We can divide the complete flow in to following sections.
- Login and token generation
- API invocation with a valid access token
- API invocation with an invalid access token (refreshing access token)
- Change settings (tenant domain and client ID)
- Logout
Now let’s go through above sub topics one by one in detail. You can get the relevant source code from this GitHub link.
Login and token generation
In the following diagram we have shown how login and token generation flows work with Authorization code grant with PKCE
.

When user opens the mobile application, user is navigated to the Login
page. For navigations, it is recommended to use Flutter routes when it comes to Flutter mobile apps. We have used navigations with named routes in this implementation.
When user opens the application, from code level it starts the app with the /login
named initial route. Therefore app starts with the Login widget. Inside that Login class it initializes the app and returns app layout with Login to Cloud
button from Widget build(BuildContext context)
.
Once user clicks Login to Cloud
button, loginAction() gets called. Inside that function, login() function initializes user login and get the access token. Inside login() function appAuth.authorize()
function pops up web browser with WSO2 Cloud's Key manager /authorize
URL. In this call AppAuth
internally generates code challenge and sends code_challenge
and code_challenger_algorithm (SHA256)
with the request since we use authorization code grant with PKCE
flow. Apart from that, client-id
, redirect URI
, scope
, grant_type
, etc are sent as query parameters in the opened URL.
Followings explain the parameters we are using in these code level authentication requests:
client_id
: Consumer ID you obtained in ‘Prerequisites to setup in WSO2 Cloud’ section by creating an application in WSO2 API Store.redirect_uri
: Call back URI we configured when creating the application/configuring constants file in ‘Prerequisites to setup in WSO2 Cloud’ section.scope
: OAuth2 scopes required (which permissions should be delegated to the client)state
: A random string used for CSRF protection. Also this gives your app a chance to persist data between the user being directed to the authorization server and back again, such as using the state parameter as a session key.
Following code snippet shows the implementation of the auth request:
final AuthorizationServiceConfiguration serviceConfiguration =
AuthorizationServiceConfiguration('https://$domain/authorize',
'https://$domain/token?tenantDomain=$TENANT_DOMAIN'); final AuthorizationResponse authorizationResponse =
await flutterAppAuth.authorize(
AuthorizationRequest(clientId, redirectUri,
issuer: 'https://$authDomain',
scopes: <String>['openid', 'profile', 'offline_access'],
serviceConfiguration: serviceConfiguration),
);
Then WSO2 API Cloud will redirect the web browser to login page. Next user gets UIs to enter login details and give consent.
Once login details are entered, WSO2 key manager validates them and redirects back to the pre-configured redirect URI with auth code if validation successful. This redirectURI
should match with the Callback URI
we configured in the API store's application.
Next from code level it invokes /token
endpoint to get tokens with the received auth code and code verifier which is generated by flutter_appauth
underneath (we can get the AppAuth generated codeVerifier
as authorizationResponse.codeVerifier
and pass it to the /token
request) since it's required in PKCE flow for security validation. /token
request is made by the following code snippet:
final TokenResponse tokenResponse = await flutterAppAuth.token(TokenRequest(
clientId, redirectUri,
serviceConfiguration: serviceConfiguration,
authorizationCode: authorizationResponse.authorizationCode,
codeVerifier: authorizationResponse.codeVerifier));
Note:
If you have not enabled “Allow authentication without client secret configuration” under the OIDC service provider config in WSO2 API Cloud as mentioned in “Prerequisites to setup in WSO2 Cloud”, you need to pass client secret in the code level request as follows:
final TokenResponse tokenResponse = await flutterAppAuth.token(TokenRequest(
clientId, redirectUri,
clientSecret: <your_client_secret>,
serviceConfiguration: serviceConfiguration,
authorizationCode: authorizationResponse.authorizationCode,
codeVerifier: authorizationResponse.codeVerifier));
With a successful response we receive refresh_token, access_token and id_token. Flutter has a library called flutter_secure_storage to securely persist data locally. Therefore once we receive tokens, we add them to secure storage (we can read those values from secure storage when it’s needed). Following code snippet show how to set and get values from secure storage:
/// Function to set access token to secure storage
Future<String> setAccessToken(String accessToken) async {
await secureStorage.write(key: 'access_token', value: accessToken);
}/// Function to get access token from secure storage
Future<String> getAccessToken() async {
return secureStorage.read(key: 'access_token');
}
API invocation with a valid access token
Following diagram shows how to invoke an already published API using a valid access token (token we retrieved from previous step).

Once login and token generation flow is successful, user is navigated to the Home widget where user can enter a capital of a country and search country details.
As an example we enter ‘colombo’ and clicks the search icon
in the right side of the search box. Then from code level invokeApiAction() function gets called. Inside that we call fetchCountries() function by passing tenant domain, input value (capital) and access token
to send a GET request to the pre-configured API context URL. fetchCountries() functionality is shown in the code snippet below:
Future<http.Response> fetchCountries(
String tenantDomain, String capital, String accessToken) async { // Full API context path (apart from URL param attached)
String API_CONTEXT_PATH =
'https://$AUTH_DOMAIN/t/$tenantDomain/demo/v1.0/capital/';
// Sends a get request to configured API context URL with access //token
final String url = '$API_CONTEXT_PATH$capital';
http.Response response = await http.get(
url,
headers: <String, String>{
'Authorization': 'Bearer $accessToken',
HttpHeaders.contentTypeHeader: ContentType.json.mimeType
},
);
return response;
}
In the happy path when we are sending this GET request to API Cloud gateway with a valid access token, WSO2 Cloud’s key manager validates the access token and then gateway calls the backend and invoke backend API. If it gets successful, gateway sends us the JSON response with status code 200. Successful responses after mapping to ‘Country’ objects are visible in the UIs as follows:

API invocation with an invalid access token (refreshing access token)
Following diagram shows an API invocation with an invalid access token:

Access tokens are getting expired after a defined period of time (by default it’s 3600s). At that kind of a situation, when user searches a capital of a country, from application level it sends the GET request to API Cloud gateway with an invalid access token. At this point, as shown in the diagram, token validation gets failed and response comes with a error message and 401 status code.
When application receives 401 response, from code level it calls to refreshAccessToken() function to refresh access token using refresh token grant. Then again sends the GET request with the newly received valid access token to API Cloud gateway as shown in the below snippet:
if (response.statusCode == 401) {
final String accessToken = await refreshAccessToken(
clientId: await getClientID(),
redirectUri: AUTH_REDIRECT_URI,
issuer: AUTH_ISSUER,
domain: AUTH_DOMAIN); // Call fetchCountries() with new access token
response = await fetchCountries(tenantDomain, capital, accessToken);
}
Note:
If you have not enabled “Allow authentication without client secret configuration” under the OIDC service provider config in WSO2 API Cloud as mentioned in “Prerequisites to setup in WSO2 Cloud”, you need to pass client secret in the code level request as follows:
final TokenResponse response = await flutterAppAuth.token(TokenRequest(
clientId, redirectUri,
clientSecret: <your_clinet_secret>,
issuer: issuer,
refreshToken: storedRefreshToken,
serviceConfiguration: serviceConfiguration));
With that user gets the expected successful results in the home page. Checking the status code for 200 and 401 runs underneath from code level so that refreshing access token and resending the GET request if we receive 401 from first request are not visible to end user.
As a practice we recommend only to refresh token if it’s expired/invalid. Unless regenerating a token for every call is very costly.
Change settings (tenant domain and client ID)
As we discussed in the Prerequisites to setup in WSO2 Cloud section, user needs to update AUTH_CLIENT_ID
and TENANT_DOMAIN
in the lib/utils/constants.dart
file. Therefore those configs will be used as default values for this application. However if user needs to try the same application for different tenants, s/he can change AUTH_CLIENT_ID
and TENANT_DOMAIN
from mobile application UI later. We have discussed how to do it in the Login, Invoke API, Change Settings and Logout section.
Once user changes tenant domain and client ID from application dialog box, those values will be saved in the secure storage. Therefore from code level it checks whether these values are available when invoking the API, if not available it will use the default values configured in the constants.dart
file. This functionality is handled in the SettingsButton class from code level.
Logout

If user wants to logout from the application, s/he needs to click power icon
in the tab bar right side corner. Then it will pop up a confirmation dialog. If user clicks Yes
, then from code level it calls logoutAction(). Then it will delete the refresh token
from secure storage and will navigate user to Login widget
as follows:
Future<void> logoutAction() async {
await clearRefreshToken(); setState(() {
isBusy = false;
}); await Navigator.pushNamedAndRemoveUntil(
context, '/login', (route) => false);
}
All sub sections we have discussed under ‘Implementation’ section cover the overall implementation of this sample application.
What’s Next
This sample mobile application can be improved further to cater your business use cases. Here we have few more suggestions that you can start with:
- Keep multiple main files (ex: main_dev.dart, main_prod.dart) and use
flutter build target
to build for different development environments or release types. You can find more details on how to do it with Flutter in following documentations:
— Medium article — Flavors in dart code
— Creating flavors for Flutter — Official documentation - Handle exceptions, error codes and error messages in a more informative way.
- Implement exponential backoff strategy on failed requests. Refer this example algorithm for more details on exponential backoff strategy.
- Users can theme WSO2 authorization login pages matching to their organization’s logo and theme. Drop an email to
cloud@wso2.com
if you are interested in trying out this with WSO2 Cloud. Find more details on theming login pages in product documentation from this link.
That’s all about implementing PKCE with WSO2 API Cloud for a Flutter based mobile application.
Hope you enjoyed the article! !
If you have any questions, drop a comment in this article or directly contact us at cloud@wso2.com.