Skip to content

mssoylu/e003_flutter_listview_crud_app_using_oauth2_secure_rest_api

 
 

Repository files navigation

e003_flutter_listview_crud_app_using_oauth2_secure_rest_api

Based On e004

Screen Record

app screen record

What

Login logical Diagram

Login logical Diagram

Step 1

add the flowing dependencies to pubspec.yaml

  crypto: any
  url_launcher: any
  uni_links: any
  http: any

Step 2

open

\android\app\src\main\AndroidManifest.xml

affter :

<intent-filter>
    <action android:name="android.intent.action.MAIN"/>
    <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>

add

<intent-filter>
    <action android:name="android.intent.action.VIEW"/>
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <!-- Custom Path data -->
    <data android:path="/auth" android:scheme="com.googleusercontent.apps.932931520457-buv2dnhgo7jjjjv5fckqltn367psbrlb"/>
</intent-filter>

full code

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.e003_flutter_listview_crud_app_using_oauth2_secure_rest_api">

    <!-- io.flutter.app.FlutterApplication is an android.app.Application that
        calls FlutterMain.startInitialization(this); in its onCreate method.
        In most cases you can leave this as-is, but you if you want to provide
        additional functionality it is fine to subclass or reimplement
        FlutterApplication and put your custom class here. -->
    <application
        android:name="io.flutter.app.FlutterApplication"
        android:label="e003_flutter_listview_crud_app_using_oauth2_secure_rest_api"
        android:icon="@mipmap/ic_launcher">
        <activity
            android:name=".MainActivity"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <!-- This keeps the window background of the activity showing
                until Flutter renders its first frame. It can be removed if
                there is no splash screen (such as the default splash screen
                defined in @style/LaunchTheme). -->
            <meta-data
                android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
                android:value="true" />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <!-- Custom Path data -->
                <data android:path="/auth" android:scheme="com.googleusercontent.apps.932931520457-buv2dnhgo7jjjjv5fckqltn367psbrlb"/>
            </intent-filter>
        </activity>
    </application>
</manifest>

Step 3 - login code

var clientId = 'mvc';
var clientSecret = 'secret';
var authority = '10.0.2.2:5010';
var authorizationEndpoint = '/connect/authorize';
var tokenEndpoint = '/connect/token';

var scope = 'openid profile app2api offline_access';
var redirectUri =
    "com.googleusercontent.apps.932931520457-buv2dnhgo7jjjjv5fckqltn367psbrlb:/auth";

Future<void> getTokenStep1([Function callback]) async {
resetParamters();
var prams = new Map<String, String>();
prams['client_id'] = clientId;
prams['client_secret'] = clientSecret;
prams['scope'] = scope;
prams['redirect_uri'] = redirectUri;
prams['nonce'] = nonce;
prams['code_challenge'] = codeChallenge;
prams['code_challenge_method'] = 'S256';
prams['response_type'] = 'code id_token';
prams['state'] = state;
var authorizationUri = Uri.http(authority, authorizationEndpoint, prams);
launch(authorizationUri.toString());
getTokenStep2(callback);
}

StreamSubscription _sub;
bool _recivedDeepLink = false;

Future<void> getTokenStep2([Function callback]) async {
_sub = getUriLinksStream().listen((Uri uri) async {
    _sub.cancel();
    _sub = null;
    _recivedDeepLink = true;
    getTokenStep3(uri, callback);
}, onError: (err) {});

_recivedDeepLink = false;

SystemChannels.lifecycle.setMessageHandler((msg) {
    if (msg == AppLifecycleState.resumed.toString()) {
    if (_sub != null && isLoggedin != null) {
        if (!_recivedDeepLink) {
        isLoggedin.complete(false);
        } else {
        if (_sub != null) {
            _sub.cancel();
            _sub = null;
        }
        }
    }
    SystemChannels.lifecycle.setMessageHandler(null);
    }
    return Future<String>(() => msg);
});
}

Future<void> getTokenStep3(Uri uri, [Function callback]) async {
var responceParts = uri.toString().split("#")[1].split("&");

var code = responceParts.singleWhere(
    (part) => part.startsWith('code='),
    orElse: () => null,
);
if (code != null) code = code.replaceFirst('code=', '');

var tokenUri = Uri.http(authority, tokenEndpoint);

var basicAuth =
    'Basic ' + base64Encode(utf8.encode('$clientId:$clientSecret'));
var formHeader = "application/x-www-form-urlencoded";

var headers = Map<String, String>();
headers['authorization'] = basicAuth;
headers['content-type'] = formHeader;

String data =
    "grant_type=authorization_code&code=$code&redirect_uri=$redirectUri&code_verifier=$codeVerifier";

var response = await http.post(
    tokenUri.toString(),
    body: data,
    headers: headers,
);
var json = jsonDecode(response.body);
accessToken = json['access_token'];
refreshToken = json['refresh_token'];
expiresAt = DateTime.now().add(Duration(seconds: json['expires_in']));

if (!isLoggedin.isCompleted) isLoggedin.complete(true);

if (callback != null) callback();
}

Future<void> getTokenStep4() async {
var tokenUri = Uri.http(authority, tokenEndpoint);

var formHeader = "application/x-www-form-urlencoded";
var headers = Map<String, String>();
headers['content-type'] = formHeader;

String data =
    "grant_type=refresh_token&refresh_token=$refreshToken&client_id=$clientId&client_secret=$clientSecret";

var response = await http.post(
    tokenUri.toString(),
    body: data,
    headers: headers,
);

var json = jsonDecode(response.body);
accessToken = json['access_token'];
refreshToken = json['refresh_token'];
expiresAt = DateTime.now().add(Duration(seconds: json['expires_in']));
}

Future<String> getTokenStep5() async {
if (isLoggedin == null) {
    isLoggedin = Completer<bool>();
    await getTokenStep1();
}

var _isLoggedin = await isLoggedin.future;
if (_isLoggedin) {
    if (!isExpired()) return accessToken;
    await getTokenStep4();
    if (!isExpired()) return accessToken;
} else {
    isLoggedin = null;
}
return null;
}

bool isExpired() {
if (expiresAt == null) return true;
// Returns true if [expiresAt] occurs after [now -500 seconds].
if (!expiresAt.isAfter(DateTime.now().add(Duration(seconds: -60))))
    return true;
return false;
}

String nonce;
String cSRFToken;
String state;
String codeVerifier;
String codeChallenge;
String accessToken;
String refreshToken;
DateTime expiresAt;
Completer<bool> isLoggedin;

void resetParamters() {
nonce = urlSafeString(createCryptoRandomString(32));
cSRFToken = urlSafeString(createCryptoRandomString(32));
state = cSRFToken;
codeVerifier = urlSafeString(createCryptoRandomString(64));
codeChallenge = urlSafeString(createSha256base64(codeVerifier));
accessToken = null;
}

String createCryptoRandomString([int length = 64]) {
// better outside
Random _random = Random.secure();
var values = List<int>.generate(length, (i) => _random.nextInt(256));
return base64Url.encode(values);
}

String createSha256base64(str) {
var bytes = utf8.encode(str);
var digest = sha256.convert(bytes);
var base64 = base64Encode(digest.bytes);
return base64;
}

String urlSafeString(str) {
return str.replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '');
}

Step 4 - LoginPageWidget

class LoginPageWidget extends StatelessWidget {
final Function() notifyParent;
LoginPageWidget({Key key, @required this.notifyParent}) : super(key: key);
@override
Widget build(BuildContext context) => Scaffold(
        body: Center(
        child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: <Widget>[
            RaisedButton(
                onPressed: () async {
                await getTokenStep5();
                if (notifyParent != null) {
                    notifyParent();
                }
                },
                child: Text("Login"),
            ),
            ],
        ),
        ),
    );
}

Step 5 - main

void main() {
runApp(MyAppWithLogin());
}

class MyAppWithLogin extends StatefulWidget {
@override
_MyAppWithLoginState createState() => _MyAppWithLoginState();
}

class _MyAppWithLoginState extends State<MyAppWithLogin> {
Future refresh() async {
    setState(() {});
}

@override
Widget build(BuildContext context) {
    var materialApp = MaterialApp(
        title: 'MY APP',
        theme: ThemeData(
        primarySwatch: Colors.blue,
        ),
        home: getHomeWidget(context));
    return materialApp;
}

Widget getHomeWidget(BuildContext context) {
    return new FutureBuilder(
    future: getHome(),
    builder: (BuildContext context, AsyncSnapshot snapshot) {
        if (snapshot.hasData) {
        return snapshot.data;
        } else {
        return CircularProgressIndicator();
        }
    },
    );
}

Future<Widget> getHome() async {
    if (isLoggedin != null) {
    var _isLoggedin = await isLoggedin.future;
    if (_isLoggedin) {
        return TasksPageWidget();
    }
    }
    return LoginPageWidget(
    notifyParent: refresh,
    );
}
}

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Dart 80.1%
  • Batchfile 14.6%
  • Objective-C 3.4%
  • Java 1.9%