Auth
How to authenticate users and authorize data access.
See our auth example.
Standard HTTP authentication and authorization
Electric syncs data over HTTP. This means that (unlike other sync engines where you have to use their specific APIs for auth) with Electric you can authenticate and authorize data access the same way you do for normal web resources like API endpoints.
Recommended pattern
The main pattern we recommend is to authorise at the Shape level.
So when you make a request to sync a shape, route it via your API, validate the user credentials and shape parameters, and then only proxy the data through if authorised.
For example, you could implement with the following steps:
- add an
Authorization
header to yourGET /shape
request - use the header to check that the client exists and has access to the requested data
- if not, return a
401
or403
status to tell the client it doesn't have access - if the client does have access, proxy the request to Electric and stream the response back to the client
Using the Typescript client
When using the Typescript client, you can pass a fetchWrapper
to the Electric client which adds your Authorization
header when Electric requests shape data.
Sample code
In the client:
const usersShape = (): ShapeStreamOptions => {
const user = loadCurrentUser()
return {
url: new URL(`/api/shapes/users`, window.location.origin).href,
headers: {
authorization: `Bearer ${user.token}`
}
}
}
export default function ExampleComponent () {
const { data: users } = useShape(usersShape())
}
Then for the /api/shapes/users
route:
export async function GET(
request: Request,
{ params }: { params: { table: string } }
) {
const url = new URL(request.url)
const { table } = params
// Construct the upstream URL
const originUrl = new URL(`http://localhost:3000/v1/shape/${table}`)
// Copy over the shape_id & offset query params that the
// Electric client adds so we return the right part of the Shape log.
url.searchParams.forEach((value, key) => {
if ([`shape_id`, `offset`].includes(key)) {
originUrl.searchParams.set(key, value)
}
})
//
// Authentication and authorization
//
const user = await loadUser(request.headers.get(`authorization`))
// If the user isn't set, return 401
if (!user) {
return new Response(`user not found`, { status: 401 })
}
// Only query data the user has access to unless they're an admin.
if (!user.roles.includes(`admin`)) {
originUrl.searchParams.set(`where`, `"org_id" = ${user.org_id}`)
}
// When proxying long-polling requests, content-encoding &
// content-length are added erroneously (saying the body is
// gzipped when it's not) so we'll just remove them to avoid
// content decoding errors in the browser.
//
// Similar-ish problem to https://github.com/wintercg/fetch/issues/23
let resp = await fetch(originUrl.toString())
if (resp.headers.get(`content-encoding`)) {
const headers = new Headers(resp.headers)
headers.delete(`content-encoding`)
headers.delete(`content-length`)
resp = new Response(resp.body, {
status: resp.status,
statusText: resp.statusText,
headers,
})
}
return resp
}
Integrating external auth services
Note that with this pattern, if you need it to, the auth endpoint that proxies the request to the Electric shape API can call out to a seperate auth service. So if you need to integrate an external auth system, you can.
Alternative auth modes
We have a GitHub Discussions label for auth feature requests. This includes:
If you would like or need alternative strategies for auth, please upvote and/or contribute to the discussions there.