How to create an Authentication middleware for a Flutter app? - authentication

This is my home.dart code and I want to write an Authentication Middleware for my app. At the moment my main.dart code looks like this:
void main() {
Get.put(MenuController());
Get.put(NavigationController());
Get.put(AuthController());
Get.put(AuthCard);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Obx(() => GetMaterialApp(
initialRoute: AuthController.instance.isAuth
? homeScreenRoute
: authenticationScreenRoute,
unknownRoute: GetPage(
name: '/not-found',
page: () => PageNotFound(),
transition: Transition.fadeIn),
getPages: [
GetPage(
name: rootRoute,
page: () {
return SiteLayout();
}),
GetPage(
name: authenticationScreenRoute,
page: () => const AuthenticationScreen()),
GetPage(name: homeScreenRoute, page: () => const HomeScreen()),
],
debugShowCheckedModeBanner: false,
title: 'BasicCode',
theme: ThemeData(
scaffoldBackgroundColor: light,
textTheme: GoogleFonts.mulishTextTheme(Theme.of(context).textTheme)
.apply(bodyColor: Colors.black),
pageTransitionsTheme: const PageTransitionsTheme(builders: {
TargetPlatform.iOS: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(),
}),
primarySwatch: Colors.blue,
),
));
}
}
And the isAuth variable that I checking at the initialRoute part of the code comes from the following line of codes, inside the auth_controller file that extends GetXController:
final _isAuth = false.obs;
bool get isAuth {
_isAuth.value= token != null;
return _isAuth.value;
}
String? get token {
if (_expiryDate != null &&
_expiryDate!.isAfter(DateTime.now()) &&
_token != null) {
return _token;
}
return null;
}
Everything seems good but the application sticks at the authentication page and won't move to home screen after the isAuth's value changed to true!
I searched for that and found another way by creating an authentication middleware. So I added the following code bellow the above code inside the main.dart file:
class AuthMiddlware extends GetMiddleware {
#override
RouteSettings? redirect(String route) => !AuthController.instance.isAuth
? const RouteSettings(name: authenticationScreenRoute)
: null;
}
But I get a red line under the redirect word with no error decription and don't know how to complete the middleware and make it work?

Example of how to implement an AuthGuard with FirebaseAuth and Getx.
(If not using FirebaseAuth, swap it to your preferred authentication provider in AuthGuardMiddleware.)
middleware.dart
import 'auth.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class AuthGuardMiddleware extends GetMiddleware {
var authService = Get.find<AuthService>();
#override
RouteSettings? redirect(String? route) {
return authService.isLoggedIn() ? null : RouteSettings(name: '/login');
}
}
auth.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:get/get.dart';
class AuthService extends GetxService {
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
bool isLoggedIn() {
return _firebaseAuth.currentUser != null;
}
// IMPLEMENT additional FirebaseAuth methods here.
}
main.dart
import 'package:get/get.dart';
import 'middleware.dart';
...
GetPage(
name: '/protected',
page: () => Protected()),
middlewares: [
AuthGuardMiddleware(),
]),
...

Copy paste :)
class AuthMiddlware extends GetMiddleware {
#override
RouteSettings? redirect(String? route) => !AuthController.instance.isAuth
? const RouteSettings(name: authenticationScreenRoute)
: null;
}

Related

Flutter ListView of data from API does not show on my web app

Hello i try to create a listView of element from data of an API for my flutter web project. I get no result showing on the page each time i try. I was supposed to show a ListView of French department; I tried to use a code more basic for desperately trying to have results on my page. So I try to use a more simple code than mine to see how it work but still have no result(Blank page under appBar). I don't think I understand really well how that's supposed to worked but I find out a tutorial that had a sample code of a random ListView of element from data of an API; but this also don't work I'm a little bit stressed out... Is there a problem with this code ? Can I have more explanation about how to get data from an API and display them on a ListView on Flutter? Thank you everyone for the help
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
const baseUrl = "https://jsonplaceholder.typicode.com";
class API {
static Future getUsers() {
var url = baseUrl + "/users";
return http.get(url);
}
}
class User {
int id;
String name;
String email;
User(int id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
User.fromJson(Map json)
: id = json['id'],
name = json['name'],
email = json['email'];
Map toJson() {
return {'id': id, 'name': name, 'email': email};
}
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
build(context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'My Http App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyListScreen(),
);
}
}
class MyListScreen extends StatefulWidget {
#override
createState() => _MyListScreenState();
}
class _MyListScreenState extends State {
var users = new List<User>();
_getUsers() {
API.getUsers().then((response) {
setState(() {
Iterable list = json.decode(response.body);
users = list.map((model) => User.fromJson(model)).toList();
});
});
}
initState() {
super.initState();
_getUsers();
}
dispose() {
super.dispose();
}
#override
build(context) {
return Scaffold(
appBar: AppBar(
title: Text("User List"),
),
body: ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
return ListTile(title: Text(users[index].name));
},
));
}
}
Try using async and await on getUsers:
static Future getUsers() async {
var url = baseUrl + "/users";
return await http.get(url);
}
I've tested your code and everything is okay, you just need to add a ProgressIndicator.
Things to note:
Show a CircularProgressIndicator when making API calls
Separate your model class and API calls classes (put them in different files for quick
future modifications)
Use FutureBuilder for tasks than take long to execute (async tasks)
For your current implementation, modify as follows:
Initialize loading
class _MyListScreenState extends State {
var users = new List();
bool _isLoading = true;
2.Update state when loading is complete
_getUsers() {
API.getUsers().then((response) {
setState(() {
_isLoading = false;
Iterable list = json.decode(response.body);
users = list.map((model) => User.fromJson(model)).toList();
});
});
}
3. Add widget in your build method
body: _isLoading
? Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
return ListTile(title: Text(users[index].name));
},
));

How can I use type-graphql and RESTDataSource

I wonder how it is possible to use RESTDataSource in type-graphql and thus cache correctly in a redis. I would be grateful for a small example.
At the moment I use the DI container to get a service, which is extended from the RestDataSource class, but this is not the right way.
BookmarkResolver.ts
import { Resolver, FieldResolver, Root, Query, Ctx, Authorized } from 'type-graphql';
import { DealService } from '../service/DealService';
import { AvailableLocale } from '../enum/AvailableLocale';
import { Bookmark } from '../entity/Bookmark';
#Resolver(_of => Bookmark)
export class BookmarkResolver {
constructor(private dealService: DealService) {}
#FieldResolver()
async wordpressDeal(#Root() bookmark: Bookmark) {
return await this.dealService.getDealById(bookmark.item_id, AvailableLocale.STAGING);
}
}
DealService.ts
import { Service } from 'typedi';
import { AbstractService } from './AbstractService';
import { AvailableLocale } from '../enum/AvailableLocale';
#Service()
export class DealService extends AbstractService {
baseURL = process.env.DEAL_SERVICE_URL;
async getDealById(dealId: string | number, locale: AvailableLocale) {
const response = await this.get(
'deals/' + dealId,
{ locale }
);
return this.dealReducer(response);
}
dealReducer(deal: any) {
return {
id: deal.id || 0,
title: deal.title
};
}
}
AbstractService.ts
import { RESTDataSource, HTTPCache } from 'apollo-datasource-rest';
import { Service } from 'typedi';
#Service()
export class AbstractService extends RESTDataSource {
constructor() {
super();
this.httpCache = new HTTPCache();
}
}
Share the RESTDataSource via ApolloServer's context. Use it in the resolver by accessing the context with the #Ctx() decorator.
1. Define a RESTDataSource
Define the data source according to the apollo-datasource-rest example.
export class TodoDataSource extends RESTDataSource {
constructor() {
super();
this.baseURL = "https://jsonplaceholder.typicode.com/todos";
}
async getTodos(): Promise<Todo[]> {
return this.get("/");
}
}
2. Create an instance of the DataSource and put it in the Context
When you start the server, add data sources to the context by defining a function that creates the data sources.
const server = new ApolloServer({
schema,
playground: true,
dataSources: () => ({
todoDataSource: new TodoDataSource(),
}),
});
3. Access the DataSource in the resolver
Use the #Ctx() decorator to access the context in the resolver so you can use the data source.
#Resolver(Todo)
export class TodoResolver {
#Query(() => [Todo])
async todos(#Ctx() context: Context) {
return context.dataSources.todoDataSource.getTodos();
}
}
Full, runnable example at https://github.com/lauriharpf/type-graphql-restdatasource

Accessing the value of a private variable in a method and making it global

I have a method that gets a token from a login, in a class that also returns the token. I need to access the token's value in other classes. i.e. It needs to be global.
The code works and I do get the correct value. I just need that value to be accessible globally.
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
return Scaffold(
appBar: new AppBarCall().getAppBar("Home"),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.search),
onPressed: () {
_oauth2();
},
),
drawer: new SideDrawer(),
body: HomePage(),
);
}
_oauth2() {
setState(() {
authenticate(values);
});
}
authenticate(...) async {
// login code
var token = tokenValue;
return token; // <---- this value needs to be global
}
}
var token;//now it is global, outside any class it the file
class _HomeState extends State<Home> {
return Scaffold(
//...
);
}
_oauth2() {
setState(() {
authenticate(values);
});
}
authenticate(...) async {
login code
token = tokenValue;//set the value here
return tokenValue;
}
}

Flutter: login through a webview

I'm pretty new to Flutter.
Is there a way to login through a webview into our app?
e.g. In the first page there is this webview where we can do the login. After we logged in, the app takes us in a second page where we can do other stuff.
In my app I use instagram implicit authentification, which implies to login user in webview and get token from redirect url. I use flutter_webview_plugin
Next code builds WebviewScaffold with login url. And it listen for url changes. So when response is redirected to my redirectUrl it parses url to get token. Then you need to save token for following requests in app.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
class LoginScreen extends StatefulWidget {
#override
_LoginScreenState createState() => new _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final flutterWebviewPlugin = new FlutterWebviewPlugin();
StreamSubscription _onDestroy;
StreamSubscription<String> _onUrlChanged;
StreamSubscription<WebViewStateChanged> _onStateChanged;
String token;
#override
void dispose() {
// Every listener should be canceled, the same should be done with this stream.
_onDestroy.cancel();
_onUrlChanged.cancel();
_onStateChanged.cancel();
flutterWebviewPlugin.dispose();
super.dispose();
}
#override
void initState() {
super.initState();
flutterWebviewPlugin.close();
// Add a listener to on destroy WebView, so you can make came actions.
_onDestroy = flutterWebviewPlugin.onDestroy.listen((_) {
print("destroy");
});
_onStateChanged =
flutterWebviewPlugin.onStateChanged.listen((WebViewStateChanged state) {
print("onStateChanged: ${state.type} ${state.url}");
});
// Add a listener to on url changed
_onUrlChanged = flutterWebviewPlugin.onUrlChanged.listen((String url) {
if (mounted) {
setState(() {
print("URL changed: $url");
if (url.startsWith(Constants.redirectUri)) {
RegExp regExp = new RegExp("#access_token=(.*)");
this.token = regExp.firstMatch(url)?.group(1);
print("token $token");
saveToken(token);
Navigator.of(context).pushNamedAndRemoveUntil(
"/home", (Route<dynamic> route) => false);
flutterWebviewPlugin.close();
}
});
}
});
}
#override
Widget build(BuildContext context) {
String loginUrl = "someservise.com/auth";
return new WebviewScaffold(
url: loginUrl,
appBar: new AppBar(
title: new Text("Login to someservise..."),
));
}
}
You can use my plugin flutter_inappwebview, which is a Flutter plugin that allows you to add inline WebViews or open an in-app browser window and has a lot of events, methods, and options to control WebViews.
You can use onLoadStartĀ or onLoadStop eventsĀ to detect URL changes. For example, you can get the token:
from the url
from cookies
from localStorage
Full example:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
Future main() async {
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: InAppWebViewPage()
);
}
}
class InAppWebViewPage extends StatefulWidget {
#override
_InAppWebViewPageState createState() => new _InAppWebViewPageState();
}
class _InAppWebViewPageState extends State<InAppWebViewPage> {
InAppWebViewController webView;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("InAppWebView")
),
body: Container(
child: Column(children: <Widget>[
Expanded(
child: Container(
child: InAppWebView(
initialUrlRequest: URLRequest(url: Uri.https("next.kemi.ai", "")),
initialOptions: InAppWebViewGroupOptions(
android: AndroidInAppWebViewOptions(
domStorageEnabled: true,
databaseEnabled: true,
),
),
onWebViewCreated: (InAppWebViewController controller) {
webView = controller;
},
onLoadStart: (InAppWebViewController controller, Uri? url) {
},
onLoadStop: (InAppWebViewController controller, Uri? url) async {
if (url?.toString().startsWith("https://myUrl.com/auth-response")??false) {
// get your token from url
RegExp regExp = RegExp("access_token=(.*)");
String? token = regExp.firstMatch(url?.toString()??'')?.group(1);
print(token);
// or using CookieManager
CookieManager cookieManager = CookieManager.instance();
Cookie? cookie = await cookieManager.getCookie(
url: Uri.parse("https://myUrl.com/auth-response"),
name: "access_token");
print(cookie?.value??'');
// or using javascript to get access_token from localStorage
String? tokenFromJSEvaluation = await controller.evaluateJavascript(source: "localStorage.getItem('access_token')");
print(tokenFromJSEvaluation);
}
},
)
),
),
]))
);
}
}
For my case, I get my data via this -
InAppWebView(
initialUrlRequest: URLRequest(url: Uri.parse(widget.url)),
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(userAgent: "random"),
//Add 'random' to 'userAgent' other wise you get error in webview for google signin.
android: AndroidInAppWebViewOptions(
domStorageEnabled: true,
databaseEnabled: true,
),
),
onWebViewCreated: (InAppWebViewController controller) {
webView = controller;
showAnimationLoader(context);
},
onLoadStop: (InAppWebViewController controller, Uri? url) async {
Get.back();
if (url?.toString().contains("my.domain.app") ?? false) {
debuggingPrint(
message: 'Code : ${url?.queryParameters['code'] ?? ''}');
debuggingPrint(
message: 'State : ${url?.queryParameters['state'] ?? ''}');
}
},
)

Angular2 Service Unit Test Fails with TypeError: undefined is not an object

I am testing my authService. This is the full Test, yet Karma tells me, authService is undefined. I have plenty of Service which AuthService depends upon, but I provided and injected them all properly.
Error: Cannot resolve all parameters for 'AuthService'(BackendService, Store, LoggerService, undefined, ErrorService). Make sure that all the parameters are decorated with Inject or have valid type annotations and that 'AuthService' is decorated with Injectable. in /var/folders/zb/tpysrhsx7hbg1dnsn4gwtqq00000gn/T/8715f9a6c29e748f52c8f59e3e1daae3.browserify (line 34976)
authservice.spec.ts
import { provide } from "#angular/core";
import { AuthHttp } from "angular2-jwt";
import { HTTP_PROVIDERS, XHRBackend } from "#angular/http";
import { MockBackend } from "#angular/http/testing";
import {
TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS
} from "#angular/platform-browser-dynamic/testing";
import {
beforeEachProviders,
inject,
beforeEach,
it,
describe,
setBaseTestProviders
} from "#angular/core/testing";
import { Subject } from "rxjs/Subject";
import { AuthService } from "./auth.service";
import { BackendService } from "../../backend/backend.service";
import { ErrorService } from "../../error/error.service";
import { LoggerService } from "../../logger/logger.service";
import { NavService } from "../../nav/nav-service/nav.service";
import { Store } from "#ngrx/store";
import { TestComponentBuilder } from "#angular/compiler/testing";
import { ToastController, AlertController } from "ionic-angular";
setBaseTestProviders(TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS);
describe("AuthService", () => {
let response = new Subject();
let tcb;
let authService;
let navService;
let backendService;
let errorService;
let store;
let loggerService;
class StubErrorService extends ErrorService {
constructor() {
super(null, null);
}
toast(title) {
console.error(title);
}
modal(title, subtitle) {
console.error(title, subtitle);
}
}
class StubBackendService extends BackendService {
}
class StubStore extends Store<any> {
}
class StubLoggerService extends LoggerService {
}
class StubNavService extends NavService {
}
// PROVIDE
beforeEachProviders(() => [
HTTP_PROVIDERS,
provide(AuthHttp, {
useValue: {
get: (url: string) => {
return response;
}
}
}),
AuthService,
TestComponentBuilder,
provide(ToastController, {useClass: null}),
provide(AlertController, {useClass: null}),
provide(ErrorService, {useClass: StubErrorService}),
provide(XHRBackend, {useClass: MockBackend}),
provide(BackendService, {useClass: StubBackendService}),
provide(Store, {useClass: StubStore}),
provide(LoggerService, {useClass: StubLoggerService}),
provide(NavService, {useClass: StubNavService})
]);
// INJECTS
beforeEach(inject([TestComponentBuilder, AuthService, ErrorService, BackendService, Store, LoggerService, NavService], (_tcb, as, es, bs, s, ls, ns) => {
tcb = _tcb;
authService = as;
navService = ns;
errorService = es;
store = s;
backendService = bs;
loggerService = ls;
}));
it("should test authservice", () => {
authService.logout();
});
});
I don't know if its relevant anymore, just to say I had an almost identical issue and I resolved it following the official docs on how to test services. Hope it helps!