Going full-stack with Flutter and Supabase – Part 1: Authentication

../authentication/auth.dart

class Auth extends StatefulWidget {
@override
_AuthState createState() => _AuthState();
}

class _AuthState extends State<Auth> {
bool showSignUp = true;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
"Corey's Corner",
),
elevation: 16.0,
actions: [
IconButton(
icon: Icon(Icons.swap_horiz),
onPressed: () {
setState(() {

showSignUp = !showSignUp;
});
})
],
),
// ternary operator
body: Container(child: showSignUp ? SignUp() : SignIn()));
}
}

../authentication/sign_in.dart

For the sake of brevity I’m going to leave all form, text and button styling out of the picture and this tutorial will only cover the signIn page.

class SignIn extends StatefulWidget {

@override
_SignInState createState() => _SignInState();
}

class _SignInState extends State<SignIn> {
AuthAPI _authAPI = AuthAPI();
final _key = GlobalKey<FormState>();
String email;
String password;
@override
Widget build(BuildContext context) {

return Container(
padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 25.0),
child: Form(
key: _key,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
SizedBox(height: 70),
Text("Sign In", style: formTitleStyle(),),
SizedBox(height: 30),
Container(
width: 400,
child: TextFormField(
decoration: textInputDecoration("Email", context),
onChanged: (val) => setState(() => email = val),
)
),
SizedBox(height: 30),
Container(
width: 400,
child: TextFormField(
obscureText: true,
decoration: textInputDecoration("Password", context),
onChanged: (val) => setState(() => password = val),
),
),
SizedBox(height: 25),
GestureDetector(
child: Text("Forgot Password ?", style: TextStyle(
fontSize: 18.0,
decoration: TextDecoration.underline
),),
onTap: (){
// todo
},
),
SizedBox(height: 25),
Container(
width: 400,
child: customRaisedIconButton("Sign In !", Icons.send, context, () async {
if(_key.currentState.validate()){
try{
var req = await
_authAPI.login(email, password);
if(req.statusCode == 200){

print(req.body);
var customer =
Customer.fromReqBody(req.body);
customer.printAttributes();
Navigator.push(context, MaterialPageRoute(
builder: (context) => MyHomePage(customer: customer)));
} else {
pushError(context);
}
} on Exception catch (e){
print(e.toString());
pushError(context);
}
}
})
)
],
)
)
);
}
void PushError(){
Navigator.push(context, MaterialPageRoute(
builder: (context) => Error()
));
}

The first thing we do is create to Strings email & password within state. In side of our text forms we call setState to set the stateful fields to the values our customer types in. Before our API call we’ll use validators to ensure that our email and password aren’t bank so we don’t make any necessary API calls.

AuthAPI _authAPI = AuthAPI();

In this line of code we initialized an instance of our AuthAPI object and store it in a variable.

Our API call is asynchronous because we have to wait for our data. We use the await statement to wait for our request. Asynchronous programming allows our code to execute non-linearly. We wrap our call in a try statement to catch any errors and we call our login function and pass it the objects stored in state with line of code:

var req = await _authAPI.login(email,  password);

Adding the supabase package

Let’s implement the logic for sign in and sign up. First, we add the supabase package to our app.

flutter pub add supabase

Next, we’ll create an AuthService that will handle anything auth related. In order to pass this service to any widget that needs it, we’ll create an InheritedWidget that will hold all services we might need.

classServicesextendsInheritedWidget{
  final AuthService authService;

  Services._({
    requiredthis.authService,
    required Widget child,
  }) : super(child: child);

  factory Services({required Widget child}) {
    final client = SupabaseClient(supabaseUrl, supabaseUrl);
    final authService = AuthService(client.auth);
    return Services._(authService: authService, child: child);
  }

  @overridebool updateShouldNotify(InheritedWidget oldWidget) {
    returnfalse;
  }

  static Services of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<Services>()!;
  }
}

Communication token

This is the second type of token to be used for any kind of request.

It is generated by the Server when the following cases happen:

  • after an authentication request;
  • when the token expired or is going to expire very soon.

Configure ios callback url

iOS default settings work with the project dependencies without any modifications. You can set the callback scheme by adding the following entry to the <dict> element present in the ios/Runner/Info.plist file:

Похожее:  HTTP аутентификация - HTTP | MDN

Creating a base api class:

Our first step is to build a BaseAPI class to hold all of the URL’s of our API. In my How To Make Flutter API Calls EasyI taught you how to use class inheritance as a means of simplifying and organizing your API calls. This class isn’t to complex it just stores the routes we will be requesting, check out the code below.

Creating a customer api class

Next we’re going to create a class to store all of the API calls for customer authentication.

Creating a customer object

When we create an object we are creating our own data type, we’re creating a blue print that outlines all the properties that each of our customers will have.

Creating a new flutter app

Now that we’ve got Supabase all set up, it’s time to start building the app. Let’s create a new Flutter app. Since this is a notes app built on Supabase, I’ll be calling it Supanotes!

flutter create supanotes

Note: if null safety was not already enabled by default, you can do this by running the command below.

cd supanotes
dart migrate --apply-changes

We’ll be making use of Dart’s null safety features, but it for whatever reason you don’t want to enable it for your app, you should still be able to follow along.

Handle the id token with parseidtoken

Your Flutter application will get an ID token that it will need to parse as a Base64 encoded string into a Map object. You’ll perform that action inside the parseIdToken() method.

Check this JSON payload to get a better sense of what a decoded ID token looks like:

Handshake token

During the communication from the Phone App to the Server, as no token exists, the very first request to send to the Server is a handshake request to ask the Server to generate an initial token, with a very short lifetime, to be used during the very next communication between the Phone App and the Server. This token is then returned to the Phone App.

How to generate a token?

As said previously, a token may contain many different pieces of information, be securized, have an expiration date and allow the Server to proceed with validation of any request.

Identity of the requestor

As the identity of the requestor is a very sensitive data, which piece of information could we use?

Integration with appauth

The very first step in setting up AppAuth against your authorization server is to configure OAuth 2.0 endpoint URLs. Your sample application involves three endpoints:

More auth features

Supabase authentication also supports third party logins, but we won’t show an example of how to use this functionality in this post. These OAuth providers are supported:

Notion of token state

In the communication protocol, we could also include the notion of token state. This information could be used at the Server side, after the token validation process, as a means of second-level flow control.

To illustrate this notion, we could imagine the following usual sequence of requests, with aside, the corresponding token state, returned by the Server:

  • handshaking -> “requires_authentication
  • authentication -> “token
  • other types of requests -> no token is returned (however if the token lifetime is short, we could consider having to generate new tokens in the middle of “usual” requests. In this case, a “token” could be returned)

Therefore, when the Server receives a request that requires an authentication, if the token is not of the state “token”, the request could be rejected.
By extension, if the Server receives an “authentication” request but the token is not of the state “requires_authentication”, it could reject the request.

Похожее:  OAuth 2.0 – хороший, плохой, злой… | Юзабилити

Part 1: authentication

Now, let’s make a very simple login page. Here is the UI code so far:

classSupanotesAppextendsStatelessWidget{
  staticconst supabaseGreen = Color.fromRGBO(101, 217, 165, 1.0);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Supanotes',
      theme: ThemeData(
        brightness: Brightness.dark,
        primarySwatch: toMaterialColor(supabaseGreen),
      ),
      home: HomePage(),
    );
  }
}

classHomePageextendsStatefulWidget{
  const HomePage();

  @override
  _HomePageState createState() => _HomePageState();
}

class_HomePageStateextendsState<HomePage> {
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();

  void _signUp() {
    
  }

  void _signIn() {
    
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).primaryColor,
        title: Text('Supanotes'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: TextField(
                controller: _emailController,
                keyboardType: TextInputType.emailAddress,
                decoration: InputDecoration(hintText: 'Email'),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: TextField(
                controller: _passwordController_,
                obscureText: true,
                decoration: InputDecoration(hintText: 'Password'),
              ),
            ),
            ElevatedButton.icon(
              onPressed: _signIn,
              icon: Icon(Icons.login),
              label: Text('Sign in'),
            ),
            ElevatedButton.icon(
              onPressed: _signUp,
              icon: Icon(Icons.app_registration),
              label: Text('Sign up'),
            ),
          ],
        ),
      ),
    );
  }

  @overridevoid dispose() {
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }
}

The toMaterialColor function is taken from this post here (thanks Filip!).

Prerequisites

Before getting started with this article, you need a working knowledge of Flutter. If you need help getting started, you can follow the codelabs on the Flutter website.

You also need to have the following installations in your machine:

Run the application

Launch either the iOS simulator or Android emulators, then run the application on all available devices like so:

flutter run -d all

I have feedback or ran into an issue

Set up auth0

Auth0 is an Identity-as-a-Service (IDaaS) platform that provides developers with features such as Social and Passwordless Login, among others, to ease online identity management.

To integrate Auth0 into your Flutter app, you need an Auth0 account. If you have an existing account, you can use it. If you don’t, click here to create a free account.

After creating an Auth0 account, follow the steps below to set up an application:

Your application should have at least one enabled Connection. Click on the “Connections” tab on your application page and switch on any database or social identity provider (e.g., Google).

Finally, navigate to the “Settings” tab on your application page and set a callback URL in the Allowed Callback URLs field. For this demo, your callback URL should be the following value:

com.auth0.flutterdemo://login-callback

Here is how it should look in your Application settings page:

Setting up supabase

Getting started with Supabase is simple, so we won’t go into much detail about its setup. Just head to the Supabase website and create a project.

The gotrue client

Supabase uses GoTrue for authentication. client.auth returns the GoTrue client, which is all the AuthService will need.

classAuthService{
  final GoTrueClient _client;

  AuthService(this._client);

  Future<bool> signUp(String email, String password) {
    
  }

  Future<bool> signIn(String email, String password) {
    
  }

  Future<bool> signOut() {
    
  }
}

All three functions will return true if the response was successful, otherwise false.

Let’s start with the signUp function:

The way to build a token

The easiest way of building a token is to concatenate a series of textual information, use a separator between each of them and then to encrypt it, using a one-direction encryption algorithm, and to transform the result to a Base64 string.

Example:

Types of tokens

In most cases, it is enough to only consider 2 types of tokens:

What is a token?

In simple words and among others, a token is something that:

  • is exchanged between the client (=requestor) and the server at each request;
  • contains pieces of information that:
  • ensure its integrity;
  • allow the Server to know and validate the identity of the requestor;
  • allow the Server to know and control when the token expires;
  • allow the Server to control the flow of operations through a control of the validity of the request;
  • must be securized against attempts of modification or re-use in an inappropriate schema
Похожее:  Первый опыт работы с Google API (на примере ContactsAPI) и OAuth2.0 на чистом HTTP / Хабр

Who generates the token?

By definition, the Server is the only one authorized to generate the tokens.

Why not firebase?

While Supabase is considered an alternative, it’s also very different. As always, there is no one right choice, and the best service to use depends on your use case. But Supabase has two stand-out features that Firebase does not have, which a lot of developers might prefer.

Install dependencies

This Flutter project requires three main dependencies:

Next, open the pubspec.yaml file located under the project root directory. Specify your project dependencies by replacing the dependencies section with the snippet below:

Conclusion and recommendations

In this post, you learned how to secure a Flutter application with Auth0 using readily available OSS libraries. It didn’t take you more than a couple of lines to connect and secure your application.

The article is intentionally simple to cover the basic flow. In a future article, we’ll cover how to secure multi page apps as well as define and call back-end APIs from your Flutter application.

I have feedback or ran into an issue

Test the final application

Well done on getting to the final stage. If you successfully followed the steps so far, you should see a login screen similar to this one in your emulator:

Go ahead and tap the “Login” button. Note that in iOS, a consent prompt comes up to notify you that the application is intending to use the system browser SSO to process the login:

The iOS prompt is an expected part of the ASWebAuthenticationSession implementation.

That should take you to the Auth0 Universal Login page in the system browser:

On this screen, either enter your credentials or click “Sign in with Google”. Either way, once you successfully log in, the profile screen renders:

Conclusion

This completes the introduction to the notion of token-based communication.

In a next article, I will explain the Server part of the communication.

Meanwhile, stay tuned and happy coding.

Foreword

When I first had to work with the notion of secured communication between a Client and a Server, I had to develop a solution that worked with old protocols like X25 or RS-232 in C-Language without using any library.

One of the requirements was to make sure any intercepted packets could not be re-used or replayed at a later stage. I then used the Kerberos protocol which involves the notion of tokens and encryption using DES.

Later on, when I designed Websites that also dealt with secured communication, I used JWT.

When I started up writing my first Phone App, I needed to find a solution to let my Server know the identity of the issuer of the requests. So I gave it a try and came to the following solution.

Configure android dependencies and callback url

flutter_secure_storage has a minSdkVersion:18 dependency, so you need to bump up the default minSdkVersion:16 provisioned by the flutter create scaffolding command.

Update the android/app/build.gradle file as follows:

    defaultConfig {
        applicationId "com.auth0.flutterdemo"
        minSdkVersion 18
        targetSdkVersion 28
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
        manifestPlaceholders =['appAuthRedirectScheme':'com.auth0.flutterdemo']}

Notice the added lines to insert the appAuthRedirectScheme variable into your defaultConfig section. The value of appAuthRedirectScheme must be in lower case letters.

1 Звезда2 Звезды3 Звезды4 Звезды5 Звезд (1 оценок, среднее: 4,00 из 5)
Загрузка...

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector