@movmo/connect-button
v0.1.1
Published
React component that initiates Movmo's OAuth 2.0 PKCE flow, so partners don't hand-roll PKCE.
Maintainers
Readme
@movmo/connect-button
A branded React component that initiates Movmo's OAuth 2.0 PKCE flow against auth.movmo.io, so partners don't hand-roll PKCE.
Why it's not a drop-in checkout
This package is distinct from @movmo/express-checkout-button.
| Package | Surface | Problem solved |
| -------------------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------- |
| @movmo/express-checkout-button | Airline booking page | Embeds a Movmo checkout wallet inside the airline's booking flow |
| @movmo/connect-button | Partner's auth / account page | Initiates an OAuth 2.0 PKCE authorization so the partner's backend can act on behalf of a Movmo traveler |
Different surface, different problem — do not substitute one for the other.
What this does NOT do
- Token exchange runs on your backend. This package hands you a
codeandcodeVerifier; your server uses both alongsideclient_secretto callPOST /v1/oauth/token. Theclient_secretcredential cannot reach a browser. - See Authentication for the full server-side token exchange flow.
- This package never stores tokens, never makes API calls, and has no server-side component.
Install
npm install @movmo/connect-button
# peer deps
npm install react react-domReact 18+ is required.
Quickstart
1. Render the button on your connect page
import { ConnectMovmoButton } from '@movmo/connect-button';
export function ConnectPage() {
return (
<ConnectMovmoButton
clientId="your-client-id"
redirectUri="https://yourapp.com/oauth/callback"
/>
);
}2. Handle the callback
On your /oauth/callback page, call completeMovmoOAuth to validate the redirect and retrieve the
codeVerifier for the server-side exchange:
import { useEffect } from 'react';
import { completeMovmoOAuth } from '@movmo/connect-button';
export function CallbackPage() {
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const state = params.get('state');
if (!code || !state) return;
completeMovmoOAuth(code, state)
.then(({ code, codeVerifier }) => {
// POST both to your backend — never expose client_secret in the browser
return fetch('/api/movmo/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code, codeVerifier }),
});
})
.catch((err) => console.error('OAuth completion failed:', err));
}, []);
return <p>Completing authorization…</p>;
}Headless usage
For full control over the button's appearance, use the useMovmoOAuth hook directly:
import { useMovmoOAuth } from '@movmo/connect-button';
export function CustomConnectButton() {
const { startOAuth, isStarting } = useMovmoOAuth({
clientId: 'your-client-id',
redirectUri: 'https://yourapp.com/oauth/callback',
onError: (err) => console.error('OAuth error:', err),
});
return (
<button onClick={startOAuth} disabled={isStarting}>
{isStarting ? 'Redirecting…' : 'Connect your Movmo account'}
</button>
);
}Prop reference
ConnectMovmoButton
All UseMovmoOAuthOptions props are also accepted (see below).
| Prop | Type | Default | Description |
| ------------- | ------------------------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------- |
| clientId | string | — | Required. OAuth client_id issued by Movmo. |
| redirectUri | string | — | Required. Redirect URI registered for your client. |
| label | string | "Connect" | Gradient-side text. Renders to the left of the white "Movmo" badge — visually reads "<label> Movmo". |
| variant | 'primary' \| 'secondary' \| 'ghost' | 'primary' | Visual style. |
| className | string | '' | Extra CSS classes merged onto the <button> element. |
| disabled | boolean | false | Disable independently of loading state. |
| ariaLabel | string | "Connect with Movmo" | Accessible label override. |
UseMovmoOAuthOptions
| Prop | Type | Default | Description |
| --------------- | --------------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| clientId | string | — | Required. |
| redirectUri | string | — | Required. |
| scope | string[] | — | See Forward-compat note. |
| authorizeUrl | string | 'https://auth.movmo.io/' | Base URL for the Movmo auth UI. The auth-ui parses OAuth params from the root URL — there is no /authorize sub-path in the deployed implementation. Override for staging ('https://auth.e2e.movmo.io/') or local testing. |
| storage | StorageAdapter | sessionStorageAdapter | Where to persist code_verifier and state between redirect legs. |
| generateState | () => string | base64url of 16 random bytes | Custom state generator (useful for testing). |
| onError | (err: OAuthError) => void | — | Called with a typed error when the flow fails. The error is also re-thrown for caller try/catch. |
Storage adapter
The default sessionStorageAdapter uses window.sessionStorage. For SSR (Next.js) or
environments where sessionStorage is unavailable, pass a custom adapter:
import type { StorageAdapter } from '@movmo/connect-button';
// Example: cookie-backed adapter for SSR / cross-tab persistence
const cookieAdapter: StorageAdapter = {
getItem: (key) => {
const match = document.cookie.match(new RegExp(`(?:^|; )${key}=([^;]*)`));
return match ? decodeURIComponent(match[1]) : null;
},
setItem: (key, value) => {
document.cookie = `${key}=${encodeURIComponent(value)}; path=/; SameSite=Strict`;
},
removeItem: (key) => {
document.cookie = `${key}=; path=/; max-age=0`;
},
};
// Pass it to the button or hook
<ConnectMovmoButton
clientId="your-client-id"
redirectUri="https://yourapp.com/oauth/callback"
storage={cookieAdapter}
/>The StorageAdapter interface is async-compatible — all methods may return Promise values.
Forward-compat note on scope
The scope[] prop is forwarded as a space-separated scope query param if provided, but Movmo
does not enforce per-scope authorization today. Until fine-grained scopes ship, the resulting
token carries the user's full RBAC permissions regardless of what you request.
Tracked under MOVMO-346. Accept that until
it ships, include scope in your integration now if you want forward-compat, knowing it has no
enforcement effect yet.
Versioning
0.x means the public API may shift between minor versions while we land Aviare and the next two
partner integrations. Pin to ~0.1.0 if you want patch-only updates:
"dependencies": {
"@movmo/connect-button": "~0.1.0"
}License
MIT © Movmo
