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
1"Todos_Flutter_2": { 2 "ClientId": "Todos_Flutter_2", 3 "RedirectUri": "http://localhost:3000/" 4},
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.
1// Flutter2 Client 2var flutter2Client = configurationSection["Todos_Flutter_2:ClientId"]; 3if (!flutter2Client.IsNullOrWhiteSpace()) 4{ 5 var redirectUrl = configurationSection["Todos_Flutter_2:RedirectUri"]; 6 await CreateClientAsync( 7 name: flutter2Client, 8 scopes: commonScopes, 9 grantTypes: new[] { "authorization_code" }, 10 requireClientSecret: false, 11 redirectUri: redirectUrl 12 ); 13}
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
1flutter create mytodoapp
Move into the folder
1cd mytodoapp
List the devices
1flutter devices
Run the app
1flutter run
4. Add dependencies
1dependencies: 2 openid_client: ^0.4.1 3 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
1<application 2 android:usesCleartextTraffic="true" 3 android:label="mytodos" 4 android:icon="@mipmap/ic_launcher">
5. Create flutter page to login and logout
1import 'package:flutter/material.dart'; 2import 'package:openid_client/openid_client.dart'; 3import 'package:openid_client/openid_client_io.dart'; 4import 'package:url_launcher/url_launcher.dart'; 5import 'dart:async'; 6 7class HomePage extends StatefulWidget { 8 HomePage({Key? key}) : super(key: key); 9 10 11 _HomePageState createState() => _HomePageState(); 12} 13 14class _HomePageState extends State<HomePage> { 15 final String _clientId = 'Todos_Flutter_2'; 16 static const String _issuer = 'https://d78170304b87.ngrok.io'; 17 final List<String> _scopes = <String>[ 18 'openid', 19 'profile', 20 'email', 21 'offline_access', 22 'Todos' 23 ]; 24 String logoutUrl = ""; 25 26 Widget build(BuildContext context) { 27 return Scaffold( 28 appBar: AppBar( 29 title: Text("Home"), 30 ), 31 body: Container( 32 child: Center( 33 child: Column( 34 crossAxisAlignment: CrossAxisAlignment.center, 35 mainAxisAlignment: MainAxisAlignment.center, 36 children: [ 37 ElevatedButton( 38 child: Text("Login"), 39 onPressed: () async { 40 var tokenInfo = await authenticate( 41 Uri.parse(_issuer), _clientId, _scopes); 42 print(tokenInfo.accessToken); 43 }, 44 ), 45 ElevatedButton( 46 child: Text("Logout"), 47 onPressed: () async { 48 logout(); 49 }, 50 ), 51 ], 52 ), 53 ), 54 ), 55 ); 56 } 57 58 Future<TokenResponse> authenticate( 59 Uri uri, String clientId, List<String> scopes) async { 60 // create the client 61 var issuer = await Issuer.discover(uri); 62 var client = new Client(issuer, clientId); 63 64 // create a function to open a browser with an url 65 urlLauncher(String url) async { 66 if (await canLaunch(url)) { 67 await launch(url, forceWebView: true, enableJavaScript: true); 68 } else { 69 throw 'Could not launch $url'; 70 } 71 } 72 73 // create an authenticator 74 var authenticator = new Authenticator( 75 client, 76 scopes: scopes, 77 urlLancher: urlLauncher, 78 port: 3000, 79 ); 80 81 // starts the authentication 82 var c = await authenticator.authorize(); 83 // close the webview when finished 84 closeWebView(); 85 86 var res = await c.getTokenResponse(); 87 setState(() { 88 logoutUrl = c.generateLogoutUrl().toString(); 89 }); 90 print(res.accessToken); 91 return res; 92 } 93 94 Future<void> logout() async { 95 if (await canLaunch(logoutUrl)) { 96 await launch(logoutUrl, forceWebView: true); 97 } else { 98 throw 'Could not launch $logoutUrl'; 99 } 100 await Future.delayed(Duration(seconds: 2)); 101 closeWebView(); 102 } 103}