@zoreal/oauth2-react 0.2.19 → 0.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -41
- package/dist/index.cjs +3 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -89,41 +89,76 @@ import { ZorealOAuthProvider } from '@zoreal/oauth2-react';
|
|
|
89
89
|
```
|
|
90
90
|
|
|
91
91
|
```tsx
|
|
92
|
-
// ZorealSignIn.tsx
|
|
93
|
-
import {
|
|
92
|
+
// ZorealSignIn.tsx — your own button, the way a production sign-in page uses it.
|
|
93
|
+
import { useState } from 'react';
|
|
94
|
+
import { useZorealLogin, ZorealBusyRing, ZorealMark } from '@zoreal/oauth2-react';
|
|
95
|
+
|
|
96
|
+
function ZorealSignIn({ onSignedIn }: { onSignedIn: () => void }) {
|
|
97
|
+
// Busy from the tap until the flow ends. On a computer that is while the
|
|
98
|
+
// pairing modal is open; on a phone it is the moment between the tap and
|
|
99
|
+
// the hand-over to the ZOREAL ID app. Cleared by every outcome below.
|
|
100
|
+
const [busy, setBusy] = useState(false);
|
|
94
101
|
|
|
95
|
-
function ZorealSignIn() {
|
|
96
102
|
// `email` (and profile.name, etc.) are returned from /userinfo on your backend.
|
|
97
103
|
const login = useZorealLogin({
|
|
98
104
|
flow: 'auth-code',
|
|
99
105
|
scope: 'openid email profile.name',
|
|
100
106
|
onSuccess: async ({ code, code_verifier, nonce }) => {
|
|
107
|
+
setBusy(false);
|
|
101
108
|
// Send ALL THREE to your backend over TLS. It calls POST /token with the
|
|
102
109
|
// code and verifier plus its client authentication, verifies the ID
|
|
103
110
|
// token's nonce is this one, then reads the email and name from
|
|
104
111
|
// /userinfo. That is where personal data is delivered.
|
|
105
|
-
await fetch('/api/auth/zoreal', {
|
|
112
|
+
const res = await fetch('/api/auth/zoreal', {
|
|
106
113
|
method: 'POST',
|
|
107
114
|
headers: { 'Content-Type': 'application/json' },
|
|
108
115
|
body: JSON.stringify({ code, code_verifier, nonce }),
|
|
109
116
|
});
|
|
117
|
+
if (res.ok) onSignedIn();
|
|
118
|
+
},
|
|
119
|
+
onError: (e) => {
|
|
120
|
+
setBusy(false);
|
|
121
|
+
console.error(e.description ?? e.error);
|
|
110
122
|
},
|
|
111
|
-
onError: (e) => console.error(e.description ?? e.error),
|
|
112
123
|
onNonOAuthError: (e) => {
|
|
113
|
-
|
|
114
|
-
|
|
124
|
+
setBusy(false);
|
|
125
|
+
// The holder declining or ignoring the request, or closing the dialog,
|
|
126
|
+
// is not an error to surface: the button is simply ready again.
|
|
127
|
+
if (e.type === 'request_denied' || e.type === 'request_expired' || e.type === 'popup_closed') return;
|
|
115
128
|
console.error(e.description ?? e.type);
|
|
116
129
|
},
|
|
117
130
|
});
|
|
118
131
|
|
|
119
|
-
return
|
|
132
|
+
return (
|
|
133
|
+
// The wrapper runs the pairing modal's light around the button while busy.
|
|
134
|
+
// `block` because this button fills its row; `radius` is the button's own.
|
|
135
|
+
<ZorealBusyRing busy={busy} radius={12} block>
|
|
136
|
+
<button
|
|
137
|
+
type="button"
|
|
138
|
+
disabled={busy}
|
|
139
|
+
aria-busy={busy}
|
|
140
|
+
onClick={() => {
|
|
141
|
+
if (busy) return;
|
|
142
|
+
setBusy(true);
|
|
143
|
+
login(); // from the click handler itself: on a phone this tap is the navigation
|
|
144
|
+
}}
|
|
145
|
+
>
|
|
146
|
+
<ZorealMark size={22} brand />
|
|
147
|
+
Continue with ZOREAL
|
|
148
|
+
</button>
|
|
149
|
+
</ZorealBusyRing>
|
|
150
|
+
);
|
|
120
151
|
}
|
|
121
152
|
```
|
|
122
153
|
|
|
123
|
-
That is the whole integration. When `login()` runs, the provider
|
|
124
|
-
pairing modal on screen
|
|
125
|
-
|
|
126
|
-
|
|
154
|
+
That is the whole integration. When `login()` runs on a computer, the provider
|
|
155
|
+
puts the pairing modal on screen. On a phone the tap itself navigates to the
|
|
156
|
+
provider, which opens the ZOREAL ID app; once the person has approved, the app
|
|
157
|
+
brings them back to this page, and the same hook finishes the sign-in and calls
|
|
158
|
+
your `onSuccess` there. So mount this component on the page the sign-in starts
|
|
159
|
+
from, and expect `onSuccess` on a fresh page load. See
|
|
160
|
+
[The pairing modal](#the-pairing-modal) for what it does and how to theme,
|
|
161
|
+
translate, time out or replace it.
|
|
127
162
|
|
|
128
163
|
## Quick start: the button (no backend, pseudonymous)
|
|
129
164
|
|
|
@@ -577,28 +612,28 @@ error path.
|
|
|
577
612
|
|
|
578
613
|
## A complete example
|
|
579
614
|
|
|
580
|
-
A full sign-in component, end to end
|
|
581
|
-
takes
|
|
582
|
-
|
|
583
|
-
|
|
615
|
+
A full sign-in component, end to end, the shape a production auth-code
|
|
616
|
+
integration takes: your own button, busy from the tap until the flow ends,
|
|
617
|
+
the SDK's pairing modal on a computer and the app hand-over on a phone,
|
|
618
|
+
`{ code, code_verifier, nonce }` to your backend on success, the human outcomes
|
|
619
|
+
treated as the non-events they are, and the return from the app on a phone
|
|
620
|
+
handled by the same hook.
|
|
584
621
|
|
|
585
622
|
```tsx
|
|
586
623
|
import { useState } from 'react';
|
|
587
|
-
import { ZorealOAuthProvider, useZorealLogin } from '@zoreal/oauth2-react';
|
|
588
|
-
import type { PairingState } from '@zoreal/oauth2-react';
|
|
624
|
+
import { ZorealOAuthProvider, useZorealLogin, ZorealBusyRing, ZorealMark } from '@zoreal/oauth2-react';
|
|
589
625
|
|
|
590
626
|
function ZorealSignIn() {
|
|
591
|
-
const [
|
|
627
|
+
const [busy, setBusy] = useState(false);
|
|
592
628
|
const [note, setNote] = useState<string | null>(null);
|
|
593
629
|
|
|
594
630
|
const login = useZorealLogin({
|
|
595
631
|
flow: 'auth-code',
|
|
596
632
|
scope: 'openid email profile.name',
|
|
597
633
|
// acr_values: 'zoreal.live', // request a fresh liveness for a step-up / high-value login
|
|
598
|
-
onPairingStateChange: setPairing,
|
|
599
634
|
|
|
600
635
|
onSuccess: async ({ code, code_verifier, nonce }) => {
|
|
601
|
-
|
|
636
|
+
setBusy(false);
|
|
602
637
|
// Post ALL THREE to YOUR backend over TLS. Your backend does the /token
|
|
603
638
|
// exchange with its client authentication, verifies the ID token
|
|
604
639
|
// (ES256 against the JWKS, iss/aud/exp, and this nonce), checks the acr
|
|
@@ -620,37 +655,38 @@ function ZorealSignIn() {
|
|
|
620
655
|
|
|
621
656
|
// An OAuth error from the provider (e.g. a scope not on your allow list).
|
|
622
657
|
onError: (e) => {
|
|
623
|
-
|
|
658
|
+
setBusy(false);
|
|
624
659
|
setNote(e.description ?? e.error); // the provider's words, verbatim
|
|
625
660
|
},
|
|
626
661
|
|
|
627
|
-
// The human outcomes: declined, expired,
|
|
628
|
-
//
|
|
662
|
+
// The human outcomes: declined, expired, the dialog closed. Not faults:
|
|
663
|
+
// the button is ready again. Do not alarm on these.
|
|
629
664
|
onNonOAuthError: (e) => {
|
|
630
|
-
|
|
665
|
+
setBusy(false);
|
|
631
666
|
if (e.type === 'request_denied') setNote('Login was declined. Try again when ready.');
|
|
632
667
|
else if (e.type === 'request_expired') setNote('That took too long. Try again.');
|
|
668
|
+
else if (e.type === 'popup_closed') setNote(null);
|
|
633
669
|
else setNote('Something went wrong. Try again.');
|
|
634
670
|
},
|
|
635
671
|
});
|
|
636
672
|
|
|
637
673
|
return (
|
|
638
674
|
<div>
|
|
639
|
-
<
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
</
|
|
653
|
-
|
|
675
|
+
<ZorealBusyRing busy={busy} radius={12} block>
|
|
676
|
+
<button
|
|
677
|
+
type="button"
|
|
678
|
+
disabled={busy}
|
|
679
|
+
aria-busy={busy}
|
|
680
|
+
onClick={() => {
|
|
681
|
+
if (busy) return;
|
|
682
|
+
setBusy(true);
|
|
683
|
+
login();
|
|
684
|
+
}}
|
|
685
|
+
>
|
|
686
|
+
<ZorealMark size={22} brand />
|
|
687
|
+
Continue with ZOREAL
|
|
688
|
+
</button>
|
|
689
|
+
</ZorealBusyRing>
|
|
654
690
|
|
|
655
691
|
{note && <p role="status">{note}</p>}
|
|
656
692
|
</div>
|
|
@@ -659,13 +695,20 @@ function ZorealSignIn() {
|
|
|
659
695
|
|
|
660
696
|
export default function App() {
|
|
661
697
|
return (
|
|
662
|
-
<ZorealOAuthProvider clientId="ast_your_asset_id">
|
|
698
|
+
<ZorealOAuthProvider clientId="ast_your_asset_id" locale="en" theme="auto">
|
|
663
699
|
<ZorealSignIn />
|
|
664
700
|
</ZorealOAuthProvider>
|
|
665
701
|
);
|
|
666
702
|
}
|
|
667
703
|
```
|
|
668
704
|
|
|
705
|
+
On a computer the provider renders the pairing modal for this button; nothing
|
|
706
|
+
here draws a QR. To draw your own instead, see
|
|
707
|
+
[Rendering it yourself](#rendering-it-yourself). On a phone there is no dialog:
|
|
708
|
+
the tap navigates to the provider, the ZOREAL ID app opens, and after the
|
|
709
|
+
approval the app reopens this page, where `useZorealLogin` finishes the sign-in
|
|
710
|
+
and `onSuccess` runs on that fresh page load.
|
|
711
|
+
|
|
669
712
|
**The backend must verify.** This component only starts the flow and forwards a
|
|
670
713
|
code; on its own it proves nothing. The security is your backend exchanging the
|
|
671
714
|
code with its client authentication and verifying the signed ID token — use a
|
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,7 @@ __export(index_exports, {
|
|
|
25
25
|
PairingModal: () => PairingModal,
|
|
26
26
|
ZorealBusyRing: () => ZorealBusyRing,
|
|
27
27
|
ZorealLogin: () => ZorealLogin,
|
|
28
|
+
ZorealMark: () => ZorealMark,
|
|
28
29
|
ZorealOAuthProvider: () => ZorealOAuthProvider,
|
|
29
30
|
hasGrantedAllScopesZoreal: () => hasGrantedAllScopesZoreal,
|
|
30
31
|
hasGrantedAnyScopeZoreal: () => hasGrantedAnyScopeZoreal,
|
|
@@ -41,7 +42,7 @@ var import_react2 = require("react");
|
|
|
41
42
|
|
|
42
43
|
// src/wire.ts
|
|
43
44
|
var WIRE_VERSION = 1;
|
|
44
|
-
var SDK_VERSION = "0.2.
|
|
45
|
+
var SDK_VERSION = "0.2.20";
|
|
45
46
|
var DEFAULT_ISSUER = "https://id.zoreal.com";
|
|
46
47
|
var POLL_INTERVAL_MS = 2e3;
|
|
47
48
|
var POLL_INTERVAL_ENROLLING_MS = 5e3;
|
|
@@ -2590,6 +2591,7 @@ function hasGrantedAnyScopeZoreal(response, firstScope, ...restScopes) {
|
|
|
2590
2591
|
PairingModal,
|
|
2591
2592
|
ZorealBusyRing,
|
|
2592
2593
|
ZorealLogin,
|
|
2594
|
+
ZorealMark,
|
|
2593
2595
|
ZorealOAuthProvider,
|
|
2594
2596
|
hasGrantedAllScopesZoreal,
|
|
2595
2597
|
hasGrantedAnyScopeZoreal,
|