Protect a Page with Login in Flutter Web
This guide shows how to create a simple Flutter application and secure access to the app with Ory. You can use this guide with both Ory Cloud and self-hosted Ory software.
This guide is perfect for you if:
- You have Flutter installed.
- You want to build an app using Flutter.
- You want to give access to your application to signed-in users only.
Before you start, watch this video to see the user flow you're going to implement:
info
You can find the code of the sample application here.
Create Flutter Web App
Run this command to create a basic Flutter application:
flutter create myapp
cd myapp
Add dio and
flutter dotenv to your
pubspec.yaml
file.
We use dio for HTTP request and flutter dotenv for environment variable management.
name: flutter_web_redirect
description: A Flutter Web App integrated with Ory.
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: ">=2.15.1 <3.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
dio: ^4.0.4
flutter_dotenv: ^5.0.2
pretty_json: 2.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^1.0.0
flutter:
uses-material-design: true
# Load our env file from the root directory
assets:
- env
Install Ory CLI
Follow this guide to install the Ory CLI on your machine.
Why do I Need the Ory CLI?
The Ory CLI includes useful functionality to manage your Ory Cloud Project. But that is not why we require it in this guide!
Ory's philosophy is to make hard things easy for you. For this reason, Ory has deployed measures against all OWASP Top 10 and implements the OWASP Authentication Cheat Sheet along other mechanisms.
Therefore, Ory manages Anti-CSRF Cookies as well as Ory Session Cookies for you. That however requires that Ory and your application run on the same domain!
If your application runs on http://localhost:3000
then Ory needs to be
available on the hostname localhost
as well (e.g. http://localhost:3001
).
That is why we need the Ory CLI, because it has a proxy included which mirrors
Ory's API endpoints on the domain of your application.
Create the Authentication service
Next, create an Authentication service in the lib/services
directory. This
service will be used to query the Ory APIs for session information.
touch lib/services/auth.dart
import 'dart:convert';
import 'package:dio/dio.dart';
class AuthService {
final Dio _dio;
Map<String, dynamic> _identity = {};
AuthService(this._dio);
Future<bool> isAuthenticated() async {
return _dio.get('/sessions/whoami').then((value) {
if (value.statusCode == 200) {
_identity = value.data;
return true;
}
return false;
});
}
get identity => _identity;
}
Add Environment Variables
Create a .env
file in the root of the project to hold the ORY_BASE_URL
variable. The value of the variable is the Ory proxy URL, for example
http://localhost:3005/.ory
.
touch .env
ORY_BASE_URL=http://localhost:3005/.ory
Update lib/main.dart
Finally, update the lib/main.dart
file to check for a session cookie on the
initial load of the application. If the cookie is found, the user can access the
application. If the cookie isn't found, the user is redirected to the login
page.
import 'dart:html';
import 'package:dio/adapter_browser.dart';
import 'package:dio/browser_imp.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_web_redirect/services/auth.dart';
import 'package:pretty_json/pretty_json.dart';
Future main() async {
// load the env file
await dotenv.load(fileName: "env");
final baseUrl = dotenv.get("ORY_BASE_URL").toString();
// create the dio client for http requests
final options = BaseOptions(
baseUrl: baseUrl,
connectTimeout: 10000,
receiveTimeout: 5000,
headers: {
"Accept": "application/json",
},
validateStatus: (status) {
// here we prevent the request from throwing an error when the status code is less than 500 (internal server error)
return status! < 500;
},
);
final dio = DioForBrowser(options);
final adapter = BrowserHttpClientAdapter();
// enable cookies support
// we need this so we can send HTTP requests to the server with the cookies stored in the browser
adapter.withCredentials = true;
dio.httpClientAdapter = adapter;
final auth = AuthService(dio);
if (!(await auth.isAuthenticated())) {
_launchURL(baseUrl);
return;
}
runApp(MyApp(dio, auth));
}
void _launchURL(String url) async {
window.open(url+'/self-service/login/browser', '_self');
}
class MyApp extends StatelessWidget {
final Dio dio;
final AuthService auth;
MyApp(this.dio, this.auth);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Ory ❤ Flutter Web', auth: auth),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title, required this.auth}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
final AuthService auth;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Session Information:${prettyJson(widget.auth.identity)}'),
],
),
),
);
}
}
Test you application
Run the following steps to get your application running:
- Start your flutter web server
flutter run -d web-server --web-port 4005
- Export the SDK configuration URL for the desired Ory project. You can use the provided playground project for testing, or export the SDK URL of your own Ory Cloud project.
info
To get your project's SDK URL, sign in at console.ory.sh, select Connect from the left navigation panel, and copy the URL from the SDK Configuration section.
- macOS
- Linux
- Windows CMD
- Windows Powershell
- Self-Hosted Ory Kratos
# This is a public Ory Cloud Project.
# Don’t submit any personally identifiable information in requests made with this project.
# Sign up for Ory Cloud at
#
# https://console.ory.sh/registration
#
# and create a free Ory Cloud Project to see your own configuration embedded in code samples!
export ORY_SDK_URL=https://{your-project-slug-here}.projects.oryapis.com
# This is a public Ory Cloud Project.
# Don’t submit any personally identifiable information in requests made with this project.
# Sign up for Ory Cloud at
#
# https://console.ory.sh/registration
#
# and create a free Ory Cloud Project to see your own configuration embedded in code samples!
export ORY_SDK_URL=https://{your-project-slug-here}.projects.oryapis.com
# This is a public Ory Cloud Project.
# Don’t submit any personally identifiable information in requests made with this project.
# Sign up for Ory Cloud at
#
# https://console.ory.sh/registration
#
# and create a free Ory Cloud Project to see your own configuration embedded in code samples!
set ORY_SDK_URL=https://{your-project-slug-here}.projects.oryapis.com
# This is a public Ory Cloud Project.
# Don’t submit any personally identifiable information in requests made with this project.
# Sign up for Ory Cloud at
#
# https://console.ory.sh/registration
#
# and create a free Ory Cloud Project to see your own configuration embedded in code samples!
$Env:ORY_SDK_URL = "https://{your-project-slug-here}.projects.oryapis.com"
Clone and run Ory Kratos locally
git clone --depth 1 --branch master https://github.com/ory/kratos.git
cd kratos
git checkout master
git pull -ff
docker-compose -f quickstart.yml -f contrib/quickstart/kratos/cloud/quickstart.yml up --build --force-recreate -d
and set the environment variable to the exposed port:
export ORY_SDK_URL=http://localhost:4433
- Run the Ory proxy to mirror the Ory API endpoints on your application's
domain (
localhost
):
ory proxy --port 3005 http://localhost:4005
- Open http://localhost:3005 to access the
application. As the initial call is made by an unauthenticated user, the
session check doesn't detect a valid session and redirects to the login page
of the defined Ory project.
From there, you can create a new account or sign in using an existing identity. When you sign in, the session becomes valid and the application shows theHome
page with the session data.
Go to Production
Going to production with your app is possible in many ways. Whether you deploy
it on Kubernetes, AWS, a VM, or a RaspberryPi is up to you. To get your app
working with Ory, your app and Ory must be available under the same common
domain (e.g. https://ory.example.com
and https://www.example.com
).
The easiest way to connect Ory to your domain is to connect Ory to a subdomain of yours. You can do this easily by adding a Custom Domain to your Cloud project!
With the custom domain set up, you do not need the Ory Proxy anymore and will use the configured custom domain in your SDK calls:
Release Build
With the flutter
cli we can build a release version of our App by running the
command below:
flutter build web
We then need an HTTP server to serve the files, we will use dhttpd.
dhttpd --host localhost --port 4005 --path build/web
tip
Follow this link to learn more about Flutter Web applications in production.