@farbenmeer/auth
v3.0.0
Published
This is a simple client library for farbenmeer's central authentication and authorization service [auth.farbenmeer.de](https://auth.farbenmeer.de).
Downloads
1,328
Keywords
Readme
@farbenmeer/auth
This is a simple client library for farbenmeer's central authentication and authorization service auth.farbenmeer.de.
Usage
Add to your project using
pnpm add @farbenmeer/auth
Set up your middleware:
// middleware.ts
export { defaultMiddleware as default } from "@farbenmeer/auth"
or
// middleware.ts
import { withAuth } from "@farbenmeer/auth"
import { NextRequest, NextResponse } from "next/server"
export default withAuth(async (req: NextRequest) => {
// your middleware here.
})
Set up callback page. This needs to be located at app/api/auth/callback.tsx
. It will render all visible output from @farbenmeer/auth
// api/auth/callback.tsx
import { CallbackPage } from "@farbenmeer/auth"
export default function Page({ searchParams }: { searchParams: Record<string, string> }) {
return <CallbackPage searchParams={searchParams} />
}
Environment
For the farbenmeer auth client to work you need to set some specific environment variables. Visit auth.farbenmeer.de/clients and create your app and a new client for this app. Copy the client ID and client secret to the environment variables
FM_AUTH_CLIENT_ID
FM_AUTH_CLIENT_SECRET
of your application.
You also need to set the variable BASE_URL
to the URL under which your app will be publicly available. For Vercel Previews this will fall back to VERCEL_URL
but for the production deployment, you must set the BASE_URL
.
Preview Deployments
If you are using preview deployments you need to register your redirect URI with the auth service so you can log in on preview deployments. This can be done in a github actions pipeline:
# .github/workflows/preview.yaml
# with all necessary environment variables set
run: pnpm registerRedirectURI
or during your build step:
{
"scripts": {
"build": "registerRedirectURI && next build"
}
}
Customizing the Auth Screen
The CallbackPage component is rendered for
- the loading state while the user is being redirected during the auth flow
- various authentication error states
it returns some markup, usually a heading, a paragraph with an error description, and some anchors for skipping the automatic redirect timeout and retrying authentication. To customize the look, wrap it in some more markup:
export default function Page({ searchParams }: { searchParams: Record<string, string> }) {
return (
<main className="bg-white rounded-md p-8 [&_a]:underline">
<CallbackPage searchParams={searchParams} />
</main>
)
}
To customize the messages shown on specific events, pass a messages object
export default function Page({ searchParams }: { searchParams: Record<string, string> }) {
return <CallbackPage searchParams={searchParams} messages={{ redirecting: "Sie werden weitergeleitet..." }} />
}
all messages are defined in the typescript type for the CallbackPage
component.
Customer CI (Coroporate Identity)
If your client requires the auth server to use a custom CI for the login and logout pages and the custom CI components have been setup on the server (as a separate folder in app/
) you can configute the variable FM_AUTH_CI
with the name of your custom CI folder to redirect all sign-in/sign-out requests.
Testing
If you need to run tests or other tools agains an app that uses this package you can use a CI (Continuous Integration) Token. Yes, this naming is ambiguous with CI as in corporate identity, sorry. To do so, go to auth.farbenmeer.de (or the server instance you are using), login with your farbenmeer account, go to the admin interface to the users-tab and set up the user you want to use for the tests.
Then create a CI Token for that user. When running requests, pass that along in the header x-fm-auth-ci-token
.
Your test browser will be logged in as the user for which you created the CI token.
This will not work in production which is detected using the VERCEL_ENV
environment variable so on instances with VERCEL_ENV==="production"
you cannot use CI-Tokens.
Tests should be executed against preview or staging environments and use a separate fm-auth app from the production app so you don't mess with prod user data.
API
All routes handled by the auth middleware will only be available to logged in users. Within such authenticated routes, in any server component, route or server action, you can now use the following APIs:
The Sign-Out button
To render a valid sign-out button you will need to set up a server action that calls signOut
and redirects to the returned URL:
"use server"
import { signOut } from "@farbenmeer/auth"
import { redirect } from "next/navigation"
export async function signOutAction() {
redirect(await signOut())
}
For a complete logout button something like
<form action={signOutAction}>
<button>Sign Out</button>
</form>
will work.
Sign-Out flow using an API route
If it is necessary to support pages that use the legacy pages-router, you need to create an API Route for the sign out flow instead of a server action.
For the action (e.G. in /app/api/sign-out/route.ts
) the following will be sufficient:
import { signOutResponse } from "@farbenmeer/auth";
export function GET() {
return signOutResponse();
}
and link to it via
<a href="/api/sign-out">Sign Out</a>
The User-Type
All following APIs are centered around the User-type
interface User {
id: number;
email: string;
fullName: string | null;
image: string | null;
roles: string[];
blocked: boolean;
}
getUser
Gets the currently logged-in user.
import { getUser } from "@farbenmeer/auth"
const user = await getUser()
console.log(user.fullName, user.email)
hasRole
Checks if the user has a specific role. If no user is provided it will fall back to getUser
to get the currently logged-in user.
This behaviour is particularly userful if you need to check the same user for multiple roles or other properties.
import { hasRole } from "@farbenmeer/auth"
const admin = await hasRole("Admin")
or
import { getUser, hasRole } from "@farbenmeer/auth"
const user = await getUser()
const admin = hasRole("Admin", user)
enforceRole
Checks if the user has a specific role and throws an error if they do not.
import { enforceRole } from "@farbenmeer/auth"
await enforceRole("Admin")
or
import { getUser, enforceRole } from "@farbenmeer/auth"
const user = await getUser()
enforceRole("Admin", user)
getUsers
Get all users of your app:
import { getUsers } from "@farbenmeer/auth"
const users = await getUsers()
Get users with specific roles:
import { getUsers } from "@farbenmeer/auth"
const users = await getUsers({ roles: ["Admin", "Editor"] })
Get users for a list of email addresses:
import { getUsers } from "@farbenmeer/auth"
const users = await getUsers({ emails: ["[email protected]", "[email protected]"] })
getUserByEmail
Get a specific user by their email address
import { getUserByEmail } from "@farbenmeer/auth"
const fred = await getUserByEmail("[email protected]")
bulkUpdateUsers
Update a bulk of users to match the objects passed to the method. This can update the fullName, email, image and roles of each user.
import { bulkUpdateUsers } from "@farbenmeer/auth"
await bulkUpdateUsers([
{ id: 1, fullName: "Fred Farbenmeer", email: "[email protected]", image: null, roles: ["Admin"] }
])
Domain
A domain means that users with an email address ending in this domain will automatically be able to log in to the app as a Viewer.
export interface Domain {
appId: string;
appName: string;
domain: string;
}
getDomains
Get a list of domains registered for current app.
import { getDomains } from "@farbenmeer/auth"
const domains = await getDomains()
putDomain
Register a domain for the current app.
import { putDomain } from "@farbenmeer/auth"
await putDomain()
deleteDomain
Remove a domain from the current app. New users with this domain will not be able to log in automatically while users that have logged in before will still be able to log in.
import { deleteDomain } from "@farbenmeer/auth"
await deleteDomain()
getExternalToken
Get an access token for a specific external auth provider.
import { getExternalToken } from "@farbenmeer/auth"
await getExternalToken("...")
providers and their respective scopes must be configures on the server.
usage in API Routes
To use the aforementioned APIs in API Routes, the request object must be explicitly passed to each function, e.G.
import { getUser } from "@farbenmeer/auth"
export function GET(req: NextRequest) {
const user = await getUser({ req })
const fred = await getUserByEmail("[email protected]", { req })
}
Migration guide v2 -> v3
The signOut
function is now async.
/// before
import { signOut } from "@farbenmeer/auth"
signOut()
/// after
import { signOut } from "@farbenmeer/auth"
await signOut()