Skip to Content
Developer APIAuthenticationOAuth 2.0 Quickstart

OAuth 2.0 Quickstart

Use the authorization code flow with PKCE (RFC 7636) when your app acts on behalf of a real user — for example, a desktop client, a personal CRM plugin, or a mobile app.

Not building a user-facing app? If you need server-to-server automation (no human in the loop), use service accounts instead.


Prerequisites

  1. Register your OAuth application with EUnifyer. For a single-org integration, a platform admin does this in Back-office → Service Accounts. Note your client_id.
  2. Identify the redirect URI your app will handle after the user authorizes.
  3. Decide which scopes your app needs.

Step 1 — Build the authorization URL

Redirect the user to Keycloak’s authorization endpoint:

GET https://auth.eunifyer.com/realms/eunify/protocol/openid-connect/auth ?response_type=code &client_id=YOUR_CLIENT_ID &redirect_uri=https://yourapp.example.com/callback &scope=openid drive:read drive:write &code_challenge=BASE64URL(SHA256(code_verifier)) &code_challenge_method=S256 &state=RANDOM_STATE_VALUE

Generate the PKCE code verifier and challenge before redirecting:

function generateCodeVerifier(): string { const bytes = crypto.getRandomValues(new Uint8Array(32)); return btoa(String.fromCharCode(...bytes)) .replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); } async function generateCodeChallenge(verifier: string): Promise<string> { const data = new TextEncoder().encode(verifier); const hash = await crypto.subtle.digest("SHA-256", data); return btoa(String.fromCharCode(...new Uint8Array(hash))) .replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); }

Store code_verifier in session storage — you need it in step 3.


Step 2 — User authorises

The user sees EUnifyer’s consent screen and grants the requested scopes. Keycloak redirects back to your redirect_uri:

https://yourapp.example.com/callback ?code=AUTHORIZATION_CODE &state=RANDOM_STATE_VALUE

Verify state matches what you sent to prevent CSRF.


Step 3 — Exchange code for tokens

curl -X POST https://auth.eunifyer.com/realms/eunify/protocol/openid-connect/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "client_id=YOUR_CLIENT_ID" \ -d "redirect_uri=https://yourapp.example.com/callback" \ -d "code=AUTHORIZATION_CODE" \ -d "code_verifier=YOUR_CODE_VERIFIER"
const tokenRes = await fetch( "https://auth.eunifyer.com/realms/eunify/protocol/openid-connect/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "authorization_code", client_id: CLIENT_ID, redirect_uri: REDIRECT_URI, code, code_verifier: codeVerifier, }), }, ); const tokens = await tokenRes.json(); // { access_token, refresh_token, expires_in, ... }
import httpx res = httpx.post( "https://auth.eunifyer.com/realms/eunify/protocol/openid-connect/token", data={ "grant_type": "authorization_code", "client_id": CLIENT_ID, "redirect_uri": REDIRECT_URI, "code": code, "code_verifier": code_verifier, }, ) res.raise_for_status() tokens = res.json()

Response:

{ "access_token": "eyJ...", "refresh_token": "eyJ...", "expires_in": 900, "refresh_expires_in": 1800, "token_type": "Bearer", "scope": "openid drive:read drive:write" }

The access_token is a short-lived RS256 JWT (15 minutes by default). Store refresh_token securely to get new access tokens without re-prompting the user.


Step 4 — Call the API

Pass the access token in the Authorization header:

curl https://api.eunifyer.com/api/v1/drive/browse \ -H "Authorization: Bearer eyJ..."
const response = await fetch("https://api.eunifyer.com/api/v1/drive/browse", { headers: { Authorization: `Bearer ${accessToken}`, }, }); const data = await response.json();

Step 5 — Refresh the token

Refresh before the access token expires:

curl -X POST https://auth.eunifyer.com/realms/eunify/protocol/openid-connect/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=refresh_token" \ -d "client_id=YOUR_CLIENT_ID" \ -d "refresh_token=YOUR_REFRESH_TOKEN"

If the refresh token has expired (or the user has revoked access), restart the authorization flow from step 1.


Revoking access

To revoke a token:

curl -X POST https://auth.eunifyer.com/realms/eunify/protocol/openid-connect/revoke \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "client_id=YOUR_CLIENT_ID" \ -d "token=ACCESS_OR_REFRESH_TOKEN"

OpenID Connect discovery

Keycloak publishes its full configuration at:

https://auth.eunifyer.com/realms/eunify/.well-known/openid-configuration

Use this for endpoint discovery — do not hardcode token or JWKS URLs.


Security checklist

  • Always use PKCE — do not use the implicit flow.
  • Never store client_secret in browser JS or a mobile app binary.
  • Validate state on every callback.
  • Use short-lived access tokens; refresh silently in the background.
  • Request only the scopes your app actually needs.