@evantra-identity/react
v0.1.1
Published
Sign in with Evantra — React, React Native and Expo components plus the OAuth 2.0 / PKCE core for Evantra Identity.
Maintainers
Readme
@evantra-identity/react
Official, public package for Sign in with Evantra — React and React Native components, plus the OAuth 2.0 / PKCE core.
- Web (React / Next.js) — provider, buttons and URL helpers
- Mobile (React Native / Expo) — framework-agnostic core with deep-link handling
- OAuth — authorization code flow with PKCE (RFC6749, RFC7636, RFC8252)
Licensed MIT.
Full walkthrough: Evantra Identity — Client Integration Guide
Install
pnpm add @evantra-identity/reactShips as ESM (type: module) with TypeScript declarations. React 18+ is
a peer dependency. Node 18+.
Not on the npm registry yet. Until the release lands, install the prebuilt tarball instead — the import path is identical:
npm install ./evantra/dist-artifacts/evantra-identity-react-0.1.0.tgzSee the Distribution Guide.
Quick start (web)
import {
EvantraIdentityProvider,
EvantraSignInButton,
EvantraRegisterButton,
} from "@evantra-identity/react";
export function AuthButtons() {
return (
<EvantraIdentityProvider
config={{
identityWebBaseUrl: "https://identity.evantradebuckman.com",
identityApiBaseUrl: "https://evantra-headquarters.onrender.com",
clientId: "evt_client_…",
}}
>
<EvantraSignInButton returnTo="/welcome">
Sign in with Evantra
</EvantraSignInButton>
<EvantraRegisterButton returnTo="/welcome">
Create account
</EvantraRegisterButton>
</EvantraIdentityProvider>
);
}returnTo must be a same-origin relative path on the identity
application. Absolute URLs are rejected, so the identity app can never
be used as an open redirect.
Full OAuth flow
import {
createEvantraAuthorizeUrl,
createEvantraPkcePair,
createEvantraState,
consumeEvantraWebCallback,
exchangeEvantraCode,
} from "@evantra-identity/react";
// 1. Start
const { verifier, challenge } = await createEvantraPkcePair();
const state = createEvantraState();
sessionStorage.setItem("evantra_pkce_verifier", verifier);
sessionStorage.setItem("evantra_oauth_state", state);
window.location.assign(
createEvantraAuthorizeUrl(
defaultIdentityApiBaseUrl(),
{
clientId: "evt_client_…",
redirectUri: "https://app.example.com/oauth/callback",
codeChallenge: challenge,
state,
},
),
);
// 2. On your callback page
const { code } = consumeEvantraWebCallback(state);
const tokens = await exchangeEvantraCode(
{ clientId: "evt_client_…" },
{
code,
codeVerifier: sessionStorage.getItem("evantra_pkce_verifier")!,
redirectUri: "https://app.example.com/oauth/callback",
},
);Why the authorize URL points at the identity API host, not the web app.
/oauth/authorizeis the API's authorization endpoint. When the visitor has no session the API redirects them to the identity web app to sign in (and register, and verify their email), then completes the request and returns the code to yourredirectUri. Pointing at the web app directly would skip the endpoint that issues the code. UsedefaultIdentityApiBaseUrl()rather than hardcoding the host so a future endpoint change needs no edit.
consumeEvantraWebCallback also strips the code from the address bar so
it is not bookmarked or shared.
Mobile (React Native / Expo)
import {
consumeEvantraNativeCallback,
exchangeEvantraCode,
createEvantraAuthorizeUrl,
createEvantraPkcePair,
createEvantraState,
defaultIdentityApiBaseUrl,
} from "@evantra-identity/react";
const REDIRECT_URI = "com.example.app://oauth/callback";
const { verifier, challenge } = await createEvantraPkcePair();
const state = createEvantraState();
await SecureStore.setItemAsync("evantra_pkce_verifier", verifier);
await SecureStore.setItemAsync("evantra_oauth_state", state);
const result = await WebBrowser.openAuthSessionAsync(
createEvantraAuthorizeUrl(
defaultIdentityApiBaseUrl(),
{
clientId: "evt_client_…",
redirectUri: REDIRECT_URI,
codeChallenge: challenge,
state,
},
),
REDIRECT_URI,
);
const { code } = consumeEvantraNativeCallback(result.url!, state);
const tokens = await exchangeEvantraCode(
{ clientId: "evt_client_…" },
{
code,
codeVerifier: (await SecureStore.getItemAsync("evantra_pkce_verifier"))!,
redirectUri: REDIRECT_URI,
},
);Registered redirect URI shapes:
| Platform | Redirect URI |
|---|---|
| Web | https://app.example.com/oauth/callback |
| iOS / Android / Expo | com.example.app://oauth/callback |
| Desktop / CLI | http://127.0.0.1:PORT/callback |
Exports
Components
| Export | Purpose |
|---|---|
| EvantraIdentityProvider | Supplies configuration via context |
| useEvantraIdentity() | Reads the current configuration |
| EvantraSignInButton | Link to the hosted sign-in page |
| EvantraRegisterButton | Link to the hosted registration page |
| EvantraAuthorizeButton | Link to /oauth/authorize |
OAuth core
| Export | Purpose |
|---|---|
| createEvantraPkcePair() | PKCE verifier + S256 challenge |
| createEvantraState() | Unguessable CSRF state value |
| createEvantraAuthorizeUrl() | Builds the authorize URL |
| createEvantraLoginUrl() | Hosted sign-in URL |
| createEvantraRegisterUrl() | Hosted registration URL |
| defaultIdentityApiBaseUrl() | The identity API origin (authorize/token/userinfo) |
| defaultIdentityWebBaseUrl() | The identity web origin (login/register screens) |
| consumeEvantraWebCallback() | Reads the code from the browser URL |
| consumeEvantraNativeCallback() | Reads the code from a deep link |
| exchangeEvantraCode() | Code → tokens |
| refreshEvantraToken() | Refresh token → tokens |
| revokeEvantraToken() | Revokes a token |
| fetchEvantraUserInfo() | Loads the user profile |
| EVANTRA_DEFAULT_SCOPE | "openid profile email" |
| EvantraOAuthError | Typed OAuth failure |
Security notes
- Always use PKCE (
S256). - Never embed a
client_secretin a browser or mobile app. Those are public clients. - Always send
stateand verify it on return.consumeEvantraWebCallback(state)andconsumeEvantraNativeCallback(url, state)throwinvalid_statewhen the value does not match. - Store the PKCE verifier in
sessionStorage(web) orSecureStore/keystore (native) — neverlocalStorage. - Exchange the code on your backend when your client is confidential.
- Check
email_verifiedbefore trusting an address. - Verify the
id_tokenbefore trusting its claims. This package does not verify ID tokens for you: signature checking needs the JWKS and is deliberately left to the caller. Use a dedicated library (jose,openid-client) against the identity service's JWKS, and validateiss,aud,expandnonce. Treat the token as untrusted until then.
See Security checklist.
