Easy integration of Supabase Authentication in your Spring Boot + htmx project!
Supabase gives us access to two important things for free:
- Hosted Postgres Server with 500 MB Database Storage
- GoTrue API for Authentication of up to 50.000 Monthly Active Users
- Supabase Authentication integration
- Spring Security configuration with application.yaml/properties
- Role-Based Access Control
- Basic Authentication
Include the dependency in your build.gradle.kts. You can look up the newest version on search.maven.org
dependencies {
implementation("de.tschuehly:htmx-supabase-spring-boot-starter:LATEST_VERSION")
}
Go to supabase.com and sign up for an account. Create a new Supabase project. Save your database password for later.
Go to your Spring App and configure your application.yaml using the Supabase API credentials.
You can find them at Project Settings -> API or https://app.supabase.com/project/yourProjectId/settings/api
supabase:
projectId: yourProjectId
anonKey: ${SUPABASE_ANON_KEY}
jwtSecret: ${SUPABASE_JWT_SECRET}
successfulLoginRedirectPage: "/account"
passwordRecoveryPage: /updatePassword
unauthenticatedPage: /unauthenticated
unauthorizedPage: /unauthorizedPage
sslOnly: false
database:
host: "aws-0-eu-central-1.pooler.supabase.com"
password: ${SUPABASE_DATABASE_PASSWORD}
anonKey
, jwtSecret
and database.password
are sensitive properties, you should set these with environment
variables.
You need to set the Site URL and the Redirect URL in your supabase dashboard as well.
You can find them at Authentication -> URL Configuration.
If you didn't mess with the server.port
property you should set it to http://localhost:8080
Now you can get started with integrating authentication. The Supabase PostgreSQL database is automatically configured for you.
You can configure public accessible paths in your application.yaml with the property supabase.public
. You can
configure access for get,post,put,delete. This is the minimal configuration for getting started:
public:
get:
- "/unauthenticated"
- "/unauthorized"
- "/api/user/logout"
post:
- "/api/user/signup"
- "/api/user/sendEmailOtp"
- "/api/user/login"
- "/api/user/jwt"
- "/api/user/sendPasswordResetEmail"
This Library is heavily optimized for HTMX, an awesome Library to build modern user interfaces with the simplicity and power of hypertext. htmx gives you access to AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attributes
You need to add this little script snippet to your index page. This will authenticate with the API after logging in with Google / confirm your email
<script>
if (window.location.hash.startsWith("#access_token")) {
htmx.ajax('POST', '/api/user/jwt', {target: '#body', swap: 'outerHTML'})
.then(window.location.hash = "")
}
</script>
<form>
<label>Email:
<input type="text" name="email"/>
</label>
<label>Password:
<input type="password" name="password"/>
</label>
<button hx-post="/api/user/signup">Submit</button>
</form>
You should get an email with a confirmation link, and if we click on that we get redirected to the page we specified with
the property: supabase.successfulLoginRedirectPage: "/account"
<form>
<label>Email:
<input type="text" name="email"/>
</label>
<label>Password:
<input type="password" name="password"/>
</label>
<button hx-post="/api/user/login">Submit</button>
</form>
You need to configure each social provider in your Supabase dashboard at Authentication -> Configuration -> Providers
If you configured Google you can just insert a link to log in with Google
<a href="https://<projectId>.supabase.co/auth/v1/authorize?provider=google">Sign In with Google</a>
<button hx-get="/api/user/logout">Logout</button>
To show info/error messages to the user you will need to implement the de.tschuehly.htmx.spring.supabase.auth.exception.handler.SupabaseExceptionHandler
interface.
After successful registration a RegistrationConfirmationEmailSent
Exception is thrown from the library
that you handle by overriding the handleSuccessfulRegistration
method.
You can find an example of a custom Exception handler here:
src/test/kotlin/de/tschuehly/htmx/spring/supabase/auth/application/CustomExceptionHandlerExample.kt
You can create a role-based access control configuration right inside your application.yaml:
supabase:
roles:
admin:
get:
- "/admin/**"
user:
post:
- "/user-feature-1/**"
With this configuration, users with the Authority ROLE_ADMIN can access any endpoints under the /admin/** path, and any user with the Authority ROLE_USER can create POST request to the endpoints under the /user-feature-1/** path.
You need to be able to set roles for a user, but there are two ways to do that:
When you go to your supabase project in the Project Settings -> API section, you can find the service_role secret. With this secret, you can set the role for any user. This way you can control user roles directly from your backend.
Here is an example curl request that sets the role of the user with the id: 381c6358-22dd-4681-81e3-c79846117511
to USER
curl -X PUT --location "http://localhost:8080/api/user/setRoles" \
-H "Cookie: JWT=service_role_secret" \
-H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" \
-d "userId=381c6358-22dd-4681-81e3-c79846117511&roles=user"
You can also elevate a normal user account that will act as a "superuser" account. This user will also ignore Row Level Security!
To do this you need to set the role of the user to "service_role" in the auth.users table;
You can do this with the following SQL:
update auth.users
set role = 'service_role'
where email = 'test@example.com';
select *
from auth.users;
After executing this SQL, this account can set the roles of other users with this form:
<form>
<label>User Id
<input name="userId" type="text">
</label>
<label>Admin Role
<input name="roles" type="checkbox" value="admin"/>
</label>
<label>User Role
<input name="roles" type="checkbox" value="user"/>
</label>
<button hx-put="/api/user/setRoles">Submit</button>
</form>
You can also set the roles of a User with a little bit of SQL:
UPDATE auth.users SET
raw_app_meta_data = jsonb_set(raw_app_meta_data,'{roles}','["admin"]'::jsonb,true)
where email = 'user@example.com';
Some applications need to be configured with Basic Authentication, for example Prometheus does not support cookie based authentication.
You can configure Basic Authentication using the supabase.basicAuth property. Then encrypt the password using the Spring Boot CLI
supabase:
basicAuth:
enabled: true
username: prometheus
password: "{bcrypt}$2a$$LVUNCy8Lht68w7KA0nobWuwyzbW8AdF3bRC25glv7M12ACAZ4PT8u"
roles:
- "ADMIN"
If you want to change the settings of the HikariCP connection pool you can do that using the supabase.datasource property
.
To change the maximum pool size for example:
supabase:
datasource:
maximum-pool-size: 30