In the latest blog post, we have seen how to implement user authentication in Flutter with Supabase. In this blog post, we will cover how to implement an account deletion feature in Flutter with Supabase without exposing the SERVER-ROLE key within the application.
Why do you need to allow users to delete their accounts within your application?
Google Play Store and Apple Store have policies that require that if you allow users to create an account from within your application, it must also allow users to request their account be deleted.
So, if you have account creation within the application and want to publish your application on the Google Play Store and Apple Store, you must provide this feature to the end users.
Delete the user’s account in Supabase
Regarding the documentation, the user can delete their account through the admin API by calling the deleteUser() method on the GoTrueAdminApi instance and passing the user’s ID.
await supabase.auth.admin
.deleteUser('715ed5db-f090-4b8c-a067-640ecee36aa0');
But, any method called on GoTrueAdminApi is considered an admin method and requires a SERVICE_ROLE key. Those methods should be called on a trusted server and the SERVICE_ROLE key should never be exposed in the Flutter application.
Instead of exposing the SERVICE_ROLE key and using GoTrueAdminApi within the Flutter application, we can create an Edge function that will be deployed on the server and call the function within the application.
Edge function for user’s account deletion
Install Supabase CLI
First, let’s ensure that we have installed Supabase CLI locally. If the Supabase CLI is not installed locally, we can install it via NPM. Open the terminal and run the following:
npm i supabase --save-dev
Create delete_user_account Edge function
After the Supabase CLI is installed, we will create a delete_user_account function locally:
npx supabase functions new delete_user_account
After the Edge function is created, we will open the index.ts file in the supabase\functions\delete_user_account directory and add the following code:
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.0.0";
console.log("Delete user account function up and running");
serve(async (req) => {
try {
//Create instance of SupabaseClient
const supabaseClient = createClient(
Deno.env.get("SUPABASE_URL") ?? "",
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "",
);
// Get the authorization header from the request.
// When you invoke the function via the client library
// it will automatically pass the authenticated user's JWT.
const authHeader = req.headers.get("Authorization");
// Get JWT from auth header
const jwt = authHeader.replace("Bearer ", "");
// Get the user object
const {
data: { user },
} = await supabaseClient.auth.getUser(jwt);
if (!user) throw new Error("No user found for JWT!");
//Call deleteUser method and pass user's ID
const { data, error } = await supabaseClient.auth.admin.deleteUser(user.id);
return new Response(JSON.stringify(data), {
headers: { "Content-Type": "application/json" },
status: 200,
});
} catch (error) {
return new Response(JSON.stringify(error), {
headers: { "Content-Type": "application/json" },
status: 400,
});
}
});
Deploy function
Let’s deploy the function to the server:
npx supabase functions deploy delete_user_account --project-ref <PROJECT_REF>
After the function is deployed, it will be available in Edge function sections on Supabase and could be called within the Flutter application. On the function details page, you can find Details, Logs, Invocations, and Metrics.
Call Edge function from the Flutter application
After the function is created, it can be called within the Flutter application by calling the invoke() method on an instance of FunctionsClient.
To extend the previous example, we can create a UserRepository and add the deleteAccount() method.
abstract class IUserRepository {
Future<void> deleteAccount();
}
@Injectable(as: IUserRepository)
class UserRepository implements IUserRepository {
final FunctionsClient functionsClient;
UserRepository(this.functionsClient);
@override
Future<void> deleteAccount() async {
await functionsClient.invoke('delete_user_account');
}
}
To make the code cleaner, we will create an extension on FunctionsClient and define the deleteAccount() method.
import 'package:supabase_flutter/supabase_flutter.dart';
extension FunctionsClientX on FunctionsClient {
Future<FunctionResponse> deleteAccount() => invoke('delete_user_account');
}
Let’s update the deleteAccount() method in the UserRepository implementation:
@Injectable(as: IUserRepository)
class UserRepository implements IUserRepository {
final FunctionsClient functionsClient;
UserRepository(this.functionsClient);
@override
Future<void> deleteAccount() async {
await functionsClient.deleteAccount();
}
}
Conclusion
In this article, we have covered how to add a delete account feature to the Flutter application, which is a required feature if the application has account creation within and should be uploaded to the Google Play Store and Apple Store. We have discovered what is the Edge function and how to create and deploy it in Supabase.
This example could be extended by adding a dialog with email or password confirmation before invoking the delete_user_account function.