Table of contents
Intro
In this post we will implement the OAuth for the Flutter app.
1. Adding a new client to the IdentityServer
first step is to create a new client for the nextjs application. list of client is available in the appsettings.json
file at DbMigrator
project.
1.1 Add the new json entry
"Todos_Flutter_2": {
"ClientId": "Todos_Flutter_2",
"RedirectUri": "http://localhost:3000/"
},
1.2 Update the CreateClientsAsync method
In the Domain
project there is a IdentityServerDataSeedContributor
class which has the CreateClientsAsync
method. This method creates the Identity server clients during the migrations. so we will update this method to include the new json entry.
// Flutter2 Client
var flutter2Client = configurationSection["Todos_Flutter_2:ClientId"];
if (!flutter2Client.IsNullOrWhiteSpace())
{
var redirectUrl = configurationSection["Todos_Flutter_2:RedirectUri"];
await CreateClientAsync(
name: flutter2Client,
scopes: commonScopes,
grantTypes: new[] { "authorization_code" },
requireClientSecret: false,
redirectUri: redirectUrl
);
}
1.3 Run the migration
Now run the migration to add the client to the DB.
2. Exposing localhost using ngrok
.\ngrok.exe http https://localhost:44354
3. Creating a flutter app
Create the flutter app
flutter create mytodoapp
Move into the folder
cd mytodoapp
List the devices
flutter devices
Run the app
flutter run
4. Add dependencies
dependencies:
openid_client: ^0.4.1
url_launcher: ^6.0.4
4.1 Update the Android Manifest
Update the android app to use the usesCleartextTraffic
you can find the android manifest in mytodoapp\android\app\src\main
<application
android:usesCleartextTraffic="true"
android:label="mytodos"
android:icon="@mipmap/ic_launcher">
5. Create flutter page to login and logout
import 'package:flutter/material.dart';
import 'package:openid_client/openid_client.dart';
import 'package:openid_client/openid_client_io.dart';
import 'package:url_launcher/url_launcher.dart';
import 'dart:async';
class HomePage extends StatefulWidget {
HomePage({Key? key}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final String _clientId = 'Todos_Flutter_2';
static const String _issuer = 'https://d78170304b87.ngrok.io';
final List<String> _scopes = <String>[
'openid',
'profile',
'email',
'offline_access',
'Todos'
];
String logoutUrl = "";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Home"),
),
body: Container(
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
child: Text("Login"),
onPressed: () async {
var tokenInfo = await authenticate(
Uri.parse(_issuer), _clientId, _scopes);
print(tokenInfo.accessToken);
},
),
ElevatedButton(
child: Text("Logout"),
onPressed: () async {
logout();
},
),
],
),
),
),
);
}
Future<TokenResponse> authenticate(
Uri uri, String clientId, List<String> scopes) async {
// create the client
var issuer = await Issuer.discover(uri);
var client = new Client(issuer, clientId);
// create a function to open a browser with an url
urlLauncher(String url) async {
if (await canLaunch(url)) {
await launch(url, forceWebView: true, enableJavaScript: true);
} else {
throw 'Could not launch $url';
}
}
// create an authenticator
var authenticator = new Authenticator(
client,
scopes: scopes,
urlLancher: urlLauncher,
port: 3000,
);
// starts the authentication
var c = await authenticator.authorize();
// close the webview when finished
closeWebView();
var res = await c.getTokenResponse();
setState(() {
logoutUrl = c.generateLogoutUrl().toString();
});
print(res.accessToken);
return res;
}
Future<void> logout() async {
if (await canLaunch(logoutUrl)) {
await launch(logoutUrl, forceWebView: true);
} else {
throw 'Could not launch $logoutUrl';
}
await Future.delayed(Duration(seconds: 2));
closeWebView();
}
}