basysKom Application Development Services

Setting up Flutter and gRPC with TLS and token-based authentication
Essential Summary
In this article, we explain how to set up secure network-based communication using Flutter and gRPC. We present practical implementation steps in a hands-on example.

Introduction

In our recent article “State of Flutter on Embedded Linux” (see the blog post), we described the state and advantages of using Flutter on embedded Linux systems. In a follow-up article (see the blog post), we presented some architectural considerations and concrete ideas on how a respective system architecture could look. One possible building block of the architecture is gRPC, which can be used for communication between the client and the backend (see gRPC project website). This enables a clean and strict separation of concerns. The HMI created with Flutter will run on mobile, desktop and embedded platforms.

In the context of the architecture described in the blog post, this article deals with the topic of security, which in most cases must be taken into account when communicating via networks. With gRPC you get a free (Apache License 2.0), high-performance and language-independent RPC framework that ensures the confidentiality, integrity and authenticity of the communicated data on the basis of HTTP/2 and TLS.

In this article, we use a minimal example to show how these technologies can be combined to have a platform-independent Flutter app communicating securely with a backend running on an embedded system via gRPC.

gRPC Support

A basic requirement for the presented approach is that the language used for the backend supports gRPC.

As known from other RPC frameworks, gRPC comes with a compiler that transforms abstract interface definitions to code for the target language. For example, if you use C++ for the backend, the compiler provides you with C++ code for the client stubs, server skeletons and message types. As a basis for communication (locally or via networks), gRPC uses the project Protocol Buffers (“Protobuf”; see project website), which comes with Protocol Buffers Compiler (“protoc”). Protobuf interface definitions are described in an abstract language in .proto files. Along with a respective language plugin, the compiler uses the .proto files to generate the code in the target language. So gRPC support means there is a plugin for protoc and a library for generic gRPC functionality which is linked with your server application.

The range of supported languages is quite extensive (see gRPC languages). In addition, there are unofficial projects that are not listed here, such as tonic for rust (see project website).

In our example we use the official Dart package (see package website) for both client and server as well as the official Dart plugin for protoc (see plugin website) to generate the code. All components at this point are developed and (at the time of writing) actively maintained by Google.

In real projects, other languages such as C++ are more likely to be used for the backend, which can be more easily connected to existing libraries, interfaces and middleware on the target. But as described in the previous blog post, for network facing services the use of memory-safe languages should be considered to avoid security issues caused by widespread memory-management errors like buffer overflows and the like.

Basic Idea

The approach is very well established and well known from web technologies, where REST APIs, HTTP, TLS and access tokens are combined to implement secure communication between client and server. The rough procedure is:

  • Users authenticate by providing their credentials over a TLS-secured channel.
  • If the user name and password are correct, the server provides a token that the client uses for all subsequent requests (until the user logs out or the token is invalidated in some other way). 

There are two key points to implementing this approach:

  • The communication between client and backend needs encryption. This is covered by using HTTP/2 with TLS.
  • There is some kind of authority capable of issuing and verifying access tokens. For simple use cases, this can be implemented directly in the backend.

In the following sections, we will show you how to put these parts together to create a minimal working app.

Project setup

The example project is available here and consists of three parts:

  1. The Flutter app itself is the main project.
  2. The demo backend is a Dart package in packages/test_server.
  3. The interface definitions and generated Dart sources are in the Dart package packages/grpc_interface. This is used by the app and the backend.

In real-world projects, 1. and 2. would be separate projects of course. One aspect that needs to be considered and decided in real-world projects is where to place the interface definitions. For example, one could place them in a separate Git repository and add it as a submodule to both the client and the backend.

Server

If you use gRPC, a server can be equipped with several services. This helps in particular to achieve a clear separation of concerns. In our demo case we have two services. The first (AuthService) is for authentication, the second (MachineService) is a placeholder for your business logic. Token validation is implemented by an interceptor for the gRPC server (see authInterceptor).

The services are defined in the already mentioned .proto interface definition files (see authentication.proto and machine.proto).

A server certificate must be provided in order to use TLS encryption. In our demo project we generate this using openssl. What is important at this stage is that even the login process is encrypted, ensuring that no credentials are transmitted in plaintext.

For the access tokens, we use JWT. The server-side generation and verification of these tokens is handled by the Dart package dart_jsonwebtoken (see package website).

Flutter App

There isn’t much to say about the app. It is as minimal as possible to test the encrypted communication, authentication/token retrieval, and token-based communication. One important thing to consider here is the difference between the client for the authentication service (used by the login screen) and the machine service (used by the home screen). While both require the certificate to securely communicate via TLS, only the machine service (understandably) needs to be equipped with a token.

Interface

The grpc_interface package only contains the interface definitions and the generated Dart sources.

In order to (re-)generate the Dart sources, protoc and the respective Dart plugin must be installed on the system. The compiler can then be used as follows to create the corresponding Dart sources from a .proto file:

protoc --dart_out=grpc:lib/generated -Iproto proto/authentication.proto -Iproto proto/machine.proto 

This creates several Dart files, of which only two are currently of interest. authentication.pb.dart defines the protobuf message types (hence the “pb”) and authentication.pbgrpc.dart provides the base classes for the client (GrpcAuthenticationClient) and server (GrpcAuthenticationServiceBase). These are abstract and must be implemented in your client and server.

Conclusion

In this article, we have shown how little it takes to lay the foundation for a platform-independent application that can run on various target systems and that communicates securely with a gRPC-based backend on your embedded system.

Thanks to the versatile language support offered by gRPC and other related open source projects, the backend can be developed in many different languages. This increases the overall flexibility and allows you to pick the right technology for your project. You might depend on existing code or start over with a modern and memory-safe language.

In our demo, we focused on the essential components and steps required to setup a minimal connection between app and server. In real projects, the interfaces will, of course, be more complex than simple request-response communication. But on top of that gRPC also supports client-, server- and bi-directional streaming (see gRPC core concepts) which should cover also more complex scenarios. Thanks to Dart’s excellent support for asynchronous programming with Streams, you can use these techniques seamlessly in your Flutter app.

Picture of Benjamin Meier

Benjamin Meier

Benjamin Meier is a software engineer and a certified Qt Developer at basysKom GmbH. He joined the company in 2020, where he has mainly been working on UI development in industrial and mobile Qt/QtQuick projects. His previous working experiences focused on software development, device connectivity and data management in a research setting. He holds a diploma in applied computer science from the University of Siegen where his primary research interest was in the field of mobile robotics.

Leave a Reply

Your email address will not be published. Required fields are marked *

More Blogarticles

basysKom Newsletter

We collect only the data you enter in this form (no IP address or information that can be derived from it). The collected data is only used in order to send you our regular newsletters, from which you can unsubscribe at any point using the link at the bottom of each newsletter. We will retain this information until you ask us to delete it permanently. For more information about our privacy policy, read Privacy Policy