Providers
Every login method users actually want, with the per-provider quirks hidden behind a single API.
The catalog
| Provider | Standard | What’s special |
|---|---|---|
| OIDC | The least surprising. PKCE optional. | |
| Apple | OAuth-2 (non-standard) | client_secret is a signed ES256 JWT from a .p8 key. response_mode=form_post. PKCE supported. |
| Microsoft (Azure AD) | OIDC | Tenant-scoped (single org) or multi-tenant. |
| Discord | OAuth-2 | No OIDC. Profile via GET /users/@me. |
| OAuth-2 | No OIDC. Profile via GET /me?fields=…. | |
| X (Twitter v2) | OAuth-2 + PKCE | Newer API; not legacy 1.1. |
| Telegram | Custom widget | Hash-verified payload; per-bot, requires explicit domain binding. |
| Matrica | Vendor SDK | Carries linked wallets and social accounts the user already aggregated there. |
| Solana wallets | Signed message | Phantom, Solflare, Backpack, MWA on Android. No transaction. |
From the App-developer view, you ask for provider: 'google' and
Synq handles everything else. The “what’s special” column matters
mostly for BYO setup.
Picking which providers to enable
Two levers on the Brand record:
providerOrder: string[]— default order shown in the sign-in modal. Default['google', 'apple', 'discord', 'microsoft'].- BYO connections — for each provider you want to ship in production at any meaningful scale, set up a per-Brand connection via BYO credentials.
Hide a provider by removing it from providerOrder or disabling its
BYO connection.
Provider-by-provider notes
The least surprising. Set up via Google Cloud Console → APIs & Services → Credentials → OAuth 2.0 Client ID (type: Web application).
Redirect URI: https://synq.id/oauth/google/<brand.oauthCallbackId>/callback.
Authorized JavaScript origin: https://synq.id.
Synq requests the openid email profile scopes by default. You
don’t need to pre-request additional scopes; Synq normalizes.
Apple
The trickiest provider, by an order of magnitude. Synq handles all of the trickiness; you only need to know this if you ship BYO Apple.
Apple’s “client secret” is a signed JWT generated fresh on every token exchange. You need three things from Apple Developer:
- Team ID — your developer team identifier.
- Service ID — created under Apple → Certificates, Identifiers &
Profiles → Identifiers → Services IDs. This is your
clientId. - Key ID + private key — created under Keys → enable Sign in
with Apple → download the
.p8file. The Key ID is the filename; the contents are PEM.
Your BYO Apple connection in Synq stores all four:
{
type: 'apple',
enabled: true,
config: {
teamId: 'XXXXXXXXXX',
clientId: 'com.acme.web',
keyId: 'YYYYYYYYYY',
privateKey: '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----',
}
}Synq mints the JWT (ES256, expires every 60 seconds) at each token
exchange. Apple is strict — extra claims like jti or nbf in
the JWT will fail with invalid_client. Synq matches Apple’s spec
exactly.
Redirect URI on the Service ID:
https://synq.id/oauth/apple/<brand.oauthCallbackId>/callback.
Set the email source to “Forward to” your support email if you let users use private-relay addresses. Synq passes through the private-relay email as-is.
Microsoft (Azure AD)
Standard OIDC over login.microsoftonline.com. Set up via Azure
Portal → App registrations → New registration.
- Single tenant: your
tenantIdis your Azure AD tenant. - Multi-tenant:
tenantId = 'common'. You will need to register your app’s redirect URI under each tenant if those tenants are configured to require admin consent.
Redirect URI:
https://synq.id/oauth/microsoft/<brand.oauthCallbackId>/callback.
In the App Registration, also set the API permissions:
openid, email, profile, User.Read. Grant admin consent on
your tenant if your org requires it.
Discord
OAuth-2 only — no OIDC. Synq fetches the profile from
GET https://discord.com/api/users/@me after exchange.
Set up via Discord Developer Portal → New Application → OAuth2.
Redirect URI: https://synq.id/oauth/discord/<brand.oauthCallbackId>/callback.
Default scopes: identify (always), email (when your App requests
email from Synq).
OAuth-2 with provider-specific quirks. Set up via Facebook for Developers → Create App.
Redirect URI: https://synq.id/oauth/facebook/<brand.oauthCallbackId>/callback.
Most apps want the email and public_profile permissions. You
must publish your Facebook App (move it out of dev mode) before it
can grant email to non-developers.
X (Twitter v2)
OAuth 2.0 with PKCE. Set up via X Developer Portal → Projects & Apps → User authentication settings.
- App type: confidential or public — public is fine if you only need user sign-in.
- Permissions: read users.
- Callback URLs:
https://synq.id/oauth/x/<brand.oauthCallbackId>/callback.
X requires explicit scopes: tweet.read users.read offline.access.
Synq sets these for you.
Telegram
Telegram does not use OAuth. Sign in with Telegram works via the Telegram Login Widget: the user clicks a Telegram-hosted button, authorizes the bot, and Telegram POSTs a signed payload back.
Two prerequisites:
- A bot. Create via @BotFather . You get a bot username and a bot token. Save both.
- Domain binding. In BotFather,
/setdomainto bind your bot to a domain. Telegram will only allow login widgets on that domain. You bind tosynq.id(since that’s the domain serving the widget); your end users don’t see the binding.
Synq’s per-Brand Telegram connection stores { botUsername, botToken }. Synq verifies the Telegram payload’s HMAC against the bot token
on receipt.
One bot per Brand. If you have multiple Brands, each Brand needs
its own bot. The default Synq bot is shared and works only on
synq.id-hosted sign-in surfaces.
Matrica
Matrica is the “I already have wallets and socials” provider — users who have aggregated their identity on Matrica can sign in to your app via Matrica and carry over their linked wallets and social accounts.
Set up via Matrica Developer Console .
Redirect URI: https://synq.id/oauth/matrica/<brand.oauthCallbackId>/callback.
Use Matrica when your audience overlaps heavily with the Solana community.
Solana wallets
Not an OAuth provider, but exposed through the same provider: 'solana'
API. The flow:
- User picks “Sign in with Wallet” in the sign-in modal.
- Synq generates a challenge (see Security).
- Wallet signs the message.
- Synq verifies the signature and the user is signed in.
Supports: Phantom, Solflare, Backpack, Glow, anything that implements the Wallet Standard. On Android, supports MWA (Mobile Wallet Adapter). On iOS, deep-links to Phantom.
Wallets are first-class as both a sign-in method and as a linkable identity provider for users who already signed in with OAuth. See Identity model.
How a Synq integrator triggers each provider
The provider is just a parameter. The SDK call is the same shape for every one of them:
// @synqid/react
const { signIn } = useSynq()
await signIn({ provider: 'google' })
await signIn({ provider: 'apple' })
await signIn({ provider: 'solana' })// Raw OIDC
const params = new URLSearchParams({
client_id, redirect_uri, response_type: 'code',
scope: 'openid profile email',
state, nonce, code_challenge, code_challenge_method: 'S256',
provider: 'google', // <-- preselect a provider; skips Synq's chooser
})
res.redirect(`https://synq.id/oauth2/authorize?${params}`)Omitting provider shows Synq’s branded sign-in modal with the full
provider list. Including it skips the chooser entirely.
Linking vs signing in
A user who is already signed in can link another provider to their identity. The flow is identical to sign-in, except Synq attaches the new provider to the existing user instead of creating a new one.
From your code:
await synq.signIn({ provider: 'apple' }) // sign in or create user
await synq.link({ provider: 'apple' }) // requires current sessionIf a sign-in returns a user that already has an email collision with
another user (e.g. the same email at Google and Apple, both already
linked to different users), Synq surfaces a conflict error and your
app should walk the user through a merge flow with confirmation from
both sides.
Mental model summary
- One API.
provider: 'X'is the only thing that changes between providers from an App’s perspective. - BYO is per-provider. Each connection stores the credentials Synq needs to act on your brand’s behalf.
- Apple is special. Anytime Apple sign-in fails in confusing ways, the answer is the JWT shape.
- Telegram is special. It’s a bot + widget, not OAuth. Per-Brand bot.
- Solana is a provider too. Same
signIn({ provider: 'solana' })call; signed message under the hood.