Skip to content

Repository files navigation

WorkOS AuthKit for Java

Two small, complete reference applications showing how a Java backend signs users in with WorkOS AuthKit using the official WorkOS Java/Kotlin SDK.

Example Stack Best for
authkit-javalin-example Javalin + jte, server-rendered Seeing the whole AuthKit flow in one ~250-line file: sign-in, sign-up, code exchange, token claims, refresh, logout
authkit-spring-boot-bff-example Spring Boot 3 + Spring Security A React (or any SPA) frontend that logs in through a Spring backend and gets a plain session cookie. Replaces an existing oauth2Login() / Auth0 setup. Supports enterprise SSO (Okta, Entra, Google Workspace, ...) through AuthKit

Which one should I run?

  • Just want to see AuthKit work in Java, end to end, in one file? Run the Javalin example. It is a complete website on its own.
  • Have a React (or Vue/Angular) frontend and a Spring Boot API, and want login to set a session cookie the way Auth0 + oauth2Login() did? Run the Spring Boot BFF example. It is the backend half only; a small placeholder page stands in for your React app. Its README starts with a numbered zero-to-logged-in quickstart.

Both examples use the same three SDK calls. Everything else is ordinary Java web code.

// 1. Send the browser to AuthKit's hosted sign-in page
String url = workos.userManagement
    .getAuthorizationUrl(clientId, redirectUri)
    .provider(UserManagementProviderEnumType.AuthKit)
    .state(randomState)
    .build();

// 2. On the callback, exchange the one-time code for the user and session tokens
Authentication auth = workos.userManagement.authenticateWithCode(clientId, code, null);
User user = auth.getUser();           // id, email, firstName, lastName, emailVerified, ...
String accessToken = auth.getAccessToken();   // JWT with sid, org_id, role, permissions
String refreshToken = auth.getRefreshToken();

// 3. (Optional) End the WorkOS session on logout
String logoutUrl = workos.userManagement.getLogoutUrl(sessionId, returnTo);

Table of contents

  1. How AuthKit works
  2. Prerequisites
  3. WorkOS dashboard setup
  4. Configure credentials
  5. Run the Javalin example
  6. Run the Spring Boot BFF example
  7. Adding enterprise SSO
  8. Code walkthrough
  9. Taking this to production
  10. Troubleshooting
  11. FAQ

How AuthKit works

AuthKit is a hosted sign-in experience. Your app never renders a login form and never handles passwords, magic links, MFA codes or SAML assertions. It does two things:

  1. Redirect the user to an authorization URL that WorkOS generates for your client ID.
  2. Exchange the one-time code WorkOS sends back for a User object and session tokens, using your server-side API key.
Browser                    Your Java backend                     WorkOS AuthKit            Identity provider
  |                              |                                    |                        |
  |  GET /login                  |                                    |                        |
  |----------------------------->|  getAuthorizationUrl(...)          |                        |
  |  302 to AuthKit              |                                    |                        |
  |<-----------------------------|                                    |                        |
  |  GET /user_management/authorize?client_id=...&state=...           |                        |
  |------------------------------------------------------------------>|                        |
  |  hosted sign-in page (email + password, Google, Microsoft, magic link, SSO, MFA...)        |
  |<------------------------------------------------------------------|                        |
  |  user authenticates                                               |<---------------------->|
  |  302 to redirect_uri?code=...&state=...                           |                        |
  |<------------------------------------------------------------------|                        |
  |  GET /callback?code=...&state=...                                 |                        |
  |----------------------------->|  verify state                      |                        |
  |                              |  authenticateWithCode(clientId, code)  (server to server)   |
  |                              |----------------------------------->|                        |
  |                              |  { user, access_token, refresh_token, organization_id }     |
  |                              |<-----------------------------------|                        |
  |  set your own session cookie, 302 to app                          |                        |
  |<-----------------------------|                                    |                        |

Key properties:

  • The API key never leaves your server. Only the client ID appears in browser-visible URLs.
  • state protects the callback. Your app generates a random value, stores it in the session, and rejects any callback that doesn't echo it back.
  • Your session stays yours. AuthKit tells you who the user is; how you keep them signed in (servlet session, Spring Security, JWT of your own, ...) is up to you. Both examples use the server-side session so the browser only ever sees an opaque cookie.
  • The access token is a JWT you can decode to get sid (WorkOS session id), org_id, role and permissions. Verify its signature against workos.userManagement.getJwksUrl(clientId) before trusting it for authorization decisions.

Prerequisites

  • JDK 17 or newer. macOS: brew install openjdk@17 then export JAVA_HOME=$(brew --prefix openjdk@17). Any Temurin / Corretto / Zulu 17+ works too. Check with java -version.
  • A WorkOS account. Sign up free at https://dashboard.workos.com/signup. Sandbox environments are free with no user limit for development.
  • No Gradle install needed. The repo ships the Gradle wrapper (./gradlew).

WorkOS dashboard setup

All of this happens once per environment in https://dashboard.workos.com.

1. Pick or create an environment

Every WorkOS project has a Sandbox and a Production environment. Each has its own API keys, client ID, users and organizations. Use Sandbox for this walkthrough.

2. Enable AuthKit and choose sign-in methods

Go to Authentication. Make sure AuthKit is enabled, then turn on the methods you want on the hosted page:

Method Notes
Email + Password On by default
Magic Auth (email code) No extra setup
Google OAuth Works out of the box in Sandbox with WorkOS's shared credentials. Add your own Google client for Production
Microsoft OAuth Same as Google
GitHub, Apple, ... Same pattern
Enterprise SSO (SAML / OIDC) Configured per organization, see Adding enterprise SSO
MFA Authentication → Multi-Factor Auth. Optional or required

3. Register the redirect URI

Go to Redirects and add the callback URL of the example you are running:

Example Redirect URI
Javalin http://localhost:7001/callback
Spring Boot BFF http://localhost:8080/auth/callback

The match is exact: scheme, host, port, path. http://localhost:7001/callback/ with a trailing slash or http://127.0.0.1:7001/callback will be rejected with redirect_uri_invalid.

4. Copy the API key and client ID

Go to API Keys.

  • Client ID is shown at the top, client_01.... It is public.
  • API key: click Create Key, copy the sk_test_... value once. It is a server-side secret.

Sandbox keys start with sk_test_, Production keys with sk_live_. The API key and client ID must come from the same environment.

5. (Optional) Customize the hosted page

Branding lets you set logo, colors and a custom domain (auth.yourcompany.com) so the AuthKit page looks like your product. Nothing in the code changes.


Configure credentials

Both examples read credentials from environment variables. For local development, copy the template and fill in the two required values:

cp .env.example .env
WORKOS_API_KEY=sk_test_...
WORKOS_CLIENT_ID=client_01...

.env is git-ignored. The Javalin example reads .env directly. Spring Boot reads normal environment variables, so export them first:

set -a; source .env; set +a

Run the Javalin example

export JAVA_HOME=$(brew --prefix openjdk@17)     # or wherever your JDK 17 lives
./gradlew :authkit-javalin-example:run

Open http://localhost:7001.

Route What it does
GET / Sign-in page, or the profile page if a session exists
GET /login Redirects to AuthKit. ?screen_hint=sign-up opens the sign-up screen, ?login_hint=a@b.com pre-fills the email
GET /callback Validates state, exchanges the code, stores user and tokens in the servlet session
GET /refresh Calls authenticateWithRefreshToken and rotates both tokens
GET /logout Clears the local session and redirects to the WorkOS logout URL, which ends the AuthKit session

After signing in you will see the user profile, linked identities (which OAuth/SSO providers the user has connected), the decoded access-token claims, the raw tokens, and buttons to refresh and log out.

Details: authkit-javalin-example/README.md.


Run the Spring Boot BFF example

This example is for the common shape where a React SPA talks to a Spring Boot API and login is a full-page navigation to a backend route. React never sees a token. Spring sets a JSESSIONID cookie. If you are migrating from Auth0 with spring-boot-starter-oauth2-client, this replaces that with the WorkOS SDK and two controller methods.

set -a; source .env; set +a
export WORKOS_REDIRECT_URI=http://localhost:8080/auth/callback
export APP_FRONTEND_URL=http://localhost:8080/          # http://localhost:5173/ when using Vite
./gradlew :authkit-spring-boot-bff-example:bootRun

Open http://localhost:8080. The bundled index.html stands in for the React app.

Route What it does
GET /auth/login Random state into the session, 302 to AuthKit. Optional ?organization_id=org_... forces a specific org's SSO connection
GET /auth/callback Verifies state, authenticateWithCode, builds a Spring Security Authentication and saves it to the HttpSession, 302 to APP_FRONTEND_URL
GET /api/me Returns { id, email, name, organizationId } as JSON. 401 without a session. This is what React calls on load
POST /auth/logout Invalidates the Spring session, 204. Requires the CSRF header
GET /auth/logout/workos Also ends the WorkOS/AuthKit session, so the next login prompts again

The React change is one line:

- <a href="/oauth2/authorization/auth0">Log in</a>
+ <a href="/auth/login">Continue with Email</a>

With a Vite dev server, proxy /auth and /api to the backend so cookies stay same-origin:

// vite.config.ts
export default defineConfig({
  server: { proxy: { '/auth': 'http://localhost:8080', '/api': 'http://localhost:8080' } },
});

Details, including CSRF handling and the security configuration: authkit-spring-boot-bff-example/README.md.


Adding enterprise SSO

AuthKit routes users to their company's identity provider automatically. No code changes are needed in either example.

  1. Create an Organization in the dashboard (Organizations → Create). One per customer company.
  2. Add the customer's email domain to the organization and verify it (Domains). Users whose email matches a verified domain are routed to that organization's connection.
  3. Create an SSO connection on the organization: Okta, Microsoft Entra, Google Workspace, OneLogin, JumpCloud, generic SAML or OIDC. You can configure it yourself or send the customer's IT admin a self-serve Admin Portal link from the same page.
  4. Wait for the connection to show Active.

Now when a user types alice@customer.com on the AuthKit page, they are sent to the customer's IdP instead of seeing a password field.

To force a specific organization's connection (useful during a pilot, or when your app already knows which tenant the user belongs to), add .organizationId("org_01...") to the authorization URL builder. The Spring example does this when WORKOS_ORGANIZATION_ID is set or ?organization_id= is passed to /auth/login.


Code walkthrough

Building the authorization URL

var builder = workos.userManagement
    .getAuthorizationUrl(clientId, redirectUri)
    .provider(UserManagementProviderEnumType.AuthKit)   // hosted UI with all enabled methods
    .state(state);                                       // random, stored in your session

// Optional refinements
builder.screenHint("sign-up");          // land on the sign-up screen
builder.loginHint("alice@example.com"); // pre-fill the email field
builder.organizationId("org_01...");    // skip email entry, go straight to this org's SSO

provider can also be GoogleOAuth, MicrosoftOAuth or GitHubOAuth to skip the AuthKit page and go directly to that provider. For a single SSO connection you can pass .connectionId(...) instead of a provider.

Handling the callback

// 1. Reject errors and forged callbacks
if (ctx.queryParam("error") != null) { /* show error_description */ }
if (!storedState.equals(ctx.queryParam("state"))) { /* reject */ }

// 2. Exchange the code. Third argument: optional ip address / user agent / invitation token.
Authentication auth = workos.userManagement.authenticateWithCode(clientId, code, null);

// 3. Decide what to keep
User user = auth.getUser();
String organizationId = auth.getOrganizationId();   // null if the user is not in an org
String accessToken = auth.getAccessToken();         // short-lived JWT
String refreshToken = auth.getRefreshToken();       // long-lived, single use

Reading the access token

The access token payload contains, among others:

Claim Meaning
sub WorkOS user id (user_01...)
sid WorkOS session id. Pass to getLogoutUrl to end the AuthKit session
org_id Organization the user signed in to, if any
role The user's role slug in that organization
permissions Permissions granted by that role
exp Expiry. Default lifetime is short (minutes); use the refresh token to get a new one

Both examples decode the payload with Jackson for display. In production, verify the signature with a JWT library against the JWKS at workos.userManagement.getJwksUrl(clientId).

Refreshing

RefreshAuthentication r = workos.userManagement
    .authenticateWithRefreshToken(clientId, refreshToken, null, null);
// r.getAccessToken(), r.getRefreshToken()  -- the old refresh token is now invalid

Logging out

Clearing your own session is enough to sign the user out of your app. To also end the AuthKit session (so the next /login prompts again instead of silently re-authenticating), redirect the browser to:

workos.userManagement.getLogoutUrl(sidFromAccessToken, "https://yourapp.com/");

Taking this to production

  • Switch to the Production environment. New sk_live_ API key, new client ID, register the production redirect URI (https://yourapp.com/callback). Configure your own Google/Microsoft OAuth credentials under Authentication.
  • Verify access tokens before making authorization decisions from role / permissions. Use any JOSE library with the JWKS URL. Decoding without verification is fine for display only.
  • Store refresh tokens server-side (encrypted at rest) if you want sessions to outlive the access token. Never send the refresh token to the browser.
  • Externalize sessions if you run more than one instance. Spring Session with Redis or JDBC works unchanged with the BFF example because the principal is Serializable.
  • Set cookie flags: Secure, HttpOnly, SameSite=Lax. Lax is required so the cookie is sent on the top-level redirect back from WorkOS.
  • Custom AuthKit domain under Branding so the sign-in page is on your domain.
  • Webhooks or Events API if you need to react to user.created, session.created, organization_membership.* and similar.

Troubleshooting

Symptom Cause and fix
AuthKit page says redirect URI is invalid (/redirect-uri-invalid) The redirect_uri in the URL is not in Redirects for this environment. Add it exactly. Check port, trailing slash, localhost vs 127.0.0.1.
State mismatch on the callback The session cookie was not sent back. Start and finish on the same hostname; do not switch between localhost and 127.0.0.1. Do not set SameSite=Strict or Secure on plain http. Start again from /login.
401 Unauthorized from authenticateWithCode API key and client ID are from different environments, or the key was deleted. Both must come from the same environment.
400 / invalid_grant from authenticateWithCode The code was already used or expired (codes are single use, valid for a few minutes). Usually a double-submit or a page refresh on the callback URL.
Google button not shown on the AuthKit page Google OAuth is disabled under Authentication.
Typing a company email shows a password form instead of SSO The email domain is not verified on an organization with an Active connection, and no organizationId was passed.
"Organization has no active connection" The organization_id points at an org whose connection is not Active, or the org lives in a different environment.
App starts but Missing WORKOS_API_KEY .env is not in the repo root, or you are running Spring Boot without exporting the variables.
Unable to locate a Java Runtime / Gradle toolchain error Install JDK 17+ and set JAVA_HOME.
redirect_uri works locally but not behind a tunnel or proxy Set WORKOS_REDIRECT_URI (and BASE_URL / APP_FRONTEND_URL) to the public URL and register that URL under Redirects.

Still stuck? Email support@workos.com with the environment client ID and a timestamp. The dashboard Events page shows every authentication attempt with its outcome.


FAQ

Do I have to use Javalin or Spring? No. The SDK is plain Java. Copy login and callback into any servlet, JAX-RS, Micronaut, Quarkus, Vert.x or Ktor handler.

Can I use Spring Security's oauth2Login() pointed at WorkOS instead? The BFF example deliberately does not. AuthKit's authorization endpoint is not a generic OIDC provider for your own app; the SDK-based exchange is the supported integration and is two calls. (WorkOS Connect exists for making WorkOS an IdP for third-party apps, which is a different use case.)

Where do users live? In WorkOS. Users in the dashboard lists every AuthKit user in the environment, with sessions, identities, organization memberships and MFA factors. Use the SDK's userManagement methods or webhooks to sync into your own database if needed.

How do I get roles and permissions? Define roles under Roles in the dashboard, assign them to organization memberships, and read role / permissions from the access token.

Is the hosted page customizable? Logo, colors, custom domain and the set of sign-in methods, all from the dashboard. For a fully custom UI, the same userManagement API supports password, magic auth, MFA and SSO flows directly (authenticateWithPassword, authenticateWithMagicAuth, authenticateWithTotp, ...).

What SDK version is this? com.workos:workos:4.14.0 on Maven Central. Method names above were checked against that version's source.


Repository layout

.
├── authkit-javalin-example/           server-rendered walkthrough of the full flow
├── authkit-spring-boot-bff-example/   Spring Boot backend-for-frontend for a React SPA
├── .env.example                       credential template (copy to .env)
├── .github/workflows/build.yml        CI: ./gradlew build on JDK 17
└── settings.gradle.kts                Gradle multi-project root
./gradlew build     # compiles both examples and runs their tests, no credentials needed

Useful links

About

WorkOS AuthKit for Java: Javalin walkthrough and Spring Boot BFF example, using the official WorkOS Java SDK

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages