Docs/Cookbook: Flutter Integration

Cookbook: Flutter Integration

Practical guidance for integrating clients and enforcing secure access.

Cookbook: Integrate Fulan Auth in a Flutter App

This cookbook shows a step-by-step integration for a Flutter mobile app using Authorization Code + PKCE.

What you will build

  • A "Sign in" button that opens the system browser to /authorize
  • A deep link callback handler that receives code + state
  • A token exchange call to /token
  • A simple in-app session (store tokens securely)

Prerequisites

  • A registered app in the Portal with:
  • client_id
  • a redirect URI registered (exact match)
  • scope allowlist (example: openid)
  • Auth Service base URL (examples assume http://localhost:8080)

Step 1: Choose a redirect URI strategy

On mobile you typically use one of these:

  1. Custom scheme redirect (example: com.example.app://auth/callback)
  2. Universal Links (iOS) / App Links (Android) using https://...

Custom scheme is the simplest to start with. Register your chosen redirect URI in the Portal.

Step 2: Add deep link handling to your app

Configure your app to receive the redirect URI.

Examples:

  • Android: add an intent-filter for your custom scheme/host
  • iOS: add URL types for your custom scheme

Once configured, your app should receive a callback like:

com.example.app://auth/callback?code=<auth_code>&state=<state>

Step 3: Generate PKCE parameters

You need:

  • code_verifier: random string stored on device for the login attempt
  • code_challenge: base64url(SHA256(code_verifier))

You can implement PKCE yourself or use a small helper package. The important requirement is that code_verifier used at /token must match the one used to create the code_challenge.

Store code_verifier and state in memory until the callback returns.

Step 4: Build the /authorize URL and open it in the browser

Create the authorize URL with:

  • response_type=code
  • client_id
  • redirect_uri
  • scope
  • code_challenge
  • code_challenge_method=S256
  • state

Example:

GET http://localhost:8080/authorize?response_type=code&client_id=<client_id>&redirect_uri=<redirect_uri>&scope=openid&code_challenge=<code_challenge>&code_challenge_method=S256&state=<state>

Open it using the platform browser (recommended), not an embedded WebView.

Step 5: Handle the callback and validate state

When the app receives the callback URL:

  1. Extract code and state
  2. Validate state matches what you generated (CSRF protection)
  3. If missing or mismatched, treat it as a failed login attempt

Step 6: Exchange the code for tokens

Call the token endpoint:

curl -sS -X POST http://localhost:8080/token \
  -H 'content-type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=authorization_code' \
  --data-urlencode 'client_id=<client_id>' \
  --data-urlencode 'code=<auth_code>' \
  --data-urlencode 'redirect_uri=<redirect_uri>' \
  --data-urlencode 'code_verifier=<code_verifier>'

In Flutter, send the same form values using your HTTP client.

Step 7: Store tokens securely

Recommended storage:

  • iOS Keychain
  • Android Keystore

Avoid storing tokens in plain shared preferences.

At minimum store:

  • access_token
  • refresh_token (if your app uses refresh)

Step 8: Call your backend / APIs with the access token

For API calls:

  • Add Authorization: Bearer <access_token>
  • Enforce authorization using scopes in the token’s scope claim

Step 9: Refresh and logout

If you use refresh tokens:

  • Refresh when access token is near expiration
  • Store refresh token securely

Logout:

  • Clear stored tokens
  • Clear any pending login state (PKCE verifier, state)

Troubleshooting

  • Redirect not returning to app: confirm deep link configuration and the exact registered redirect URI.
  • Token exchange fails: ensure code_verifier matches the original code_challenge.
  • State mismatch: ensure you persist state until the callback returns.
  • Scope rejected: request only scopes that are in the app allowlist.