Skip to content

Commit 723cf88

Browse files
Merge pull request #6283 from okta/tbs-okta-889035-spa-authjs-react-update
AuthJS SDK: Sign in to SPA - update React examples
2 parents 38f6775 + 1aa1b0b commit 723cf88

3 files changed

Lines changed: 178 additions & 40 deletions

File tree

Lines changed: 152 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,198 @@
1-
Review the simple password-only sign-in use case from the sample app. This use case is outlined in the following sequence diagram with your single-page app (SPA) as the client:
1+
Review the simple password-only sign-in use case from the sample app.
22

3+
<!-- The sequence diagram below is out of date. It shows the old Step Mode
4+
flow: a single idx.authenticate(username, password) call. The current flow
5+
instead primes and submits each remediation step in turn with proceed({
6+
step }). This comment hides the diagram until the design team updates it.
7+
See OKTA-1220704 for the tracking ticket.
38
<div class="full">
49
510
![Sequence diagram that displays the interactions between the resource owner, SDK, authorization server, and resource server for a basic SPA password sign-in flow.](/img/oie-embedded-sdk/password-only-spa-authjs-flow.svg)
611
712
</div>
13+
-->
814

915
### Set up the Okta configuration settings
1016

11-
Review the `src/config.js` file that references the required [app integration settings](#app-integration-settings) to initialize your Okta Auth JS instance. The `config.js` file references the values that you add to the `testenv` file.
17+
Review the `src/config.js` file that references the required [app integration settings](#app-integration-settings) to initialize your Okta Auth JS instance. The `config.js` file references the values that you add to the `.env` file.
1218

1319
```JavaScript
14-
const CLIENT_ID = process.env.SPA_CLIENT_ID || process.env.CLIENT_ID || '{clientId}';
15-
const ISSUER = process.env.ISSUER || 'https://{yourOktaDomain}/oauth2/default';
16-
const REDIRECT_URI = `{window.location.origin}/login/callback`;
20+
const CLIENT_ID = import.meta.env.VITE_CLIENT_ID || '{clientId}';
21+
const ISSUER = import.meta.env.VITE_ISSUER || 'https://{yourOktaDomain}/oauth2/default';
22+
const REDIRECT_URI = `${window.location.origin}/login/callback`;
1723

18-
// eslint-disable-next-line import/no-anonymous-default-export
1924
export default {
2025
clientId: CLIENT_ID,
2126
issuer: ISSUER,
2227
redirectUri: REDIRECT_URI,
2328
scopes: ['openid', 'offline_access', 'profile', 'email'],
29+
pkce: true
2430
};
2531
```
2632

33+
> **Note:** Okta Auth JS 8.x requires every `idx.proceed()` call to name the remediation step it's submitting. This guide uses that Step Mode pattern throughout. See [Step Mode vs Legacy Mode](https://github.com/okta/okta-auth-js/blob/master/docs/idx.md#step-mode-vs-legacy-mode) in the Auth JS SDK docs if you're migrating an older app that relies on the deprecated, generic remediation-driven pattern.
34+
2735
### Instantiate the Okta Auth JS client
2836

29-
Review the React `app.js` file that imports the required libraries and instantiates the Okta Auth JS client with values from the `config.js`.
37+
Review the React `app.js` file that imports the required libraries and instantiates the Okta Auth JS client with values from the `config.js`. Wrap your app in the `Security` component from the Okta React SDK so it can manage the Auth JS instance for you.
3038

3139
```JavaScript
3240
import { useEffect, useState } from 'react';
3341
import { useHistory } from 'react-router-dom';
34-
import { OktaAuth, IdxStatus, urlParamsToObject, hasErrorInUrl } from '@okta/okta-auth-js';
42+
import { OktaAuth, IdxStatus, toRelativeUrl } from '@okta/okta-auth-js';
43+
import { Security } from '@okta/okta-react';
3544
import { formTransformer } from './formTransformer';
3645
import oidcConfig from './config';
3746
import './App.css';
3847

39-
function createOktaAuthInstance() {
40-
const { state } = urlParamsToObject(window.location.search);
41-
return new OktaAuth(Object.assign({}, oidcConfig, {
42-
state
43-
}));
48+
const oktaAuth = new OktaAuth(oidcConfig);
49+
50+
...
51+
52+
function App() {
53+
const history = useHistory();
54+
const restoreOriginalUri = async (_oktaAuth, originalUri) => {
55+
history.replace(toRelativeUrl(originalUri || '/', window.location.origin));
56+
};
57+
58+
return (
59+
<Security oktaAuth={oktaAuth} restoreOriginalUri={restoreOriginalUri}>
60+
...
61+
</Security>
62+
);
4463
}
64+
```
4565

46-
const oktaAuth = createOktaAuthInstance();
66+
> **Note:** This guide's simplified example doesn't cover redirect callbacks (for example, social IdP sign-in or email magic links). If your app needs them, see [Redirect callbacks](https://github.com/okta/okta-auth-js/blob/master/docs/idx.md#redirect-callbacks) in the Auth JS SDK docs and the `LoginCallback` component in [Okta's reference sample app](https://github.com/okta/okta-auth-js/tree/master/samples/generated/react-embedded-auth-with-sdk).
4767
48-
...
68+
### Start the sign-in transaction
69+
70+
Before you can render a sign-in form, you need an in-progress IDX transaction to drive it. Start one when your component mounts by calling `idx.start()`. `idx.start()` begins the transaction. It doesn't resolve a step's field data until you name that step. Call `idx.proceed()` with only a `step` name and no other values. The SDK then returns that step's `inputs` for rendering. For this password-only use case, the sign-in flow always begins with the `identify` step, which asks for `username`:
71+
72+
```JavaScript
73+
const [transaction, setTransaction] = useState(null);
74+
75+
useEffect(() => {
76+
const startTransaction = async () => {
77+
await oktaAuth.idx.start();
78+
const newTransaction = await oktaAuth.idx.proceed({ step: 'identify' });
79+
setTransaction(newTransaction);
80+
};
81+
startTransaction();
82+
}, []);
4983
```
5084

85+
Pass `transaction.nextStep` into `formTransformer` to render the form fields for the current step. See [Basic sign-in flow](#basic-sign-in-flow) for the full form code. Okta's Identity Engine collects the username first. It then challenges for the password on a separate step. This password-only flow therefore renders two forms in sequence, not one combined form.
86+
5187
### Handle the password authentication
5288

53-
Review the `apps.js` file for details on handling a successful password authentication by receiving the `SUCCESS` status and storing the returned tokens:
89+
Name the step you're submitting on every `idx.proceed()` call. `transaction.nextStep.name` holds that name. It matches the step the form already rendered. Submitting a step's values reveals the name of the next step. It does not reveal that step's renderable field data. Call `idx.proceed()` again with just the step name to prime the next form before you render it. Review the `app.js` file for details on handling a successful password authentication. It receives the `SUCCESS` status and stores the returned tokens:
5490

5591
```JavaScript
56-
....
57-
5892
const handleSubmit = async e => {
59-
e.preventDefault();
93+
e.preventDefault();
6094

61-
const newTransaction = await oktaAuth.idx.proceed(inputValues); // inputValues = username, password
62-
console.log('Transaction:', newTransaction);
95+
const submittedTransaction = await oktaAuth.idx.proceed({ step: transaction.nextStep.name, ...inputValues });
96+
console.log('Transaction:', submittedTransaction);
97+
setInputValues({});
6398

64-
setInputValues({});
65-
if (newTransaction.status === IdxStatus.SUCCESS) {
66-
oktaAuth.tokenManager.setTokens(newTransaction.tokens);
67-
} else {
99+
if (submittedTransaction.status === IdxStatus.SUCCESS) {
100+
oktaAuth.tokenManager.setTokens(submittedTransaction.tokens);
101+
return;
102+
}
103+
104+
const newTransaction = await oktaAuth.idx.proceed({ step: submittedTransaction.nextStep.name });
105+
setTransaction(newTransaction);
106+
};
107+
```
108+
109+
For this password-only use case, `handleSubmit` runs twice. The first run submits `username` from the `identify` step and primes the `challenge-authenticator` step. The second run submits `password` from that step and reaches `IdxStatus.SUCCESS`.
110+
111+
### The full sign-in component code
112+
113+
With the pieces above in place, here's the complete `App.jsx` for the password-only sign-in flow, with everything shown together:
114+
115+
```JavaScript
116+
import { useEffect, useState } from 'react';
117+
import { useHistory } from 'react-router-dom';
118+
import { OktaAuth, IdxStatus, toRelativeUrl } from '@okta/okta-auth-js';
119+
import { Security } from '@okta/okta-react';
120+
import { formTransformer } from './formTransformer';
121+
import oidcConfig from './config';
122+
import './App.css';
123+
124+
const oktaAuth = new OktaAuth(oidcConfig);
125+
126+
function SignInForm() {
127+
const [transaction, setTransaction] = useState(null);
128+
const [inputValues, setInputValues] = useState({});
129+
130+
useEffect(() => {
131+
const startTransaction = async () => {
132+
await oktaAuth.idx.start();
133+
const newTransaction = await oktaAuth.idx.proceed({ step: 'identify' });
68134
setTransaction(newTransaction);
135+
};
136+
startTransaction();
137+
}, []);
138+
139+
const handleChange = ({ target: { name, value } }) => {
140+
setInputValues({ ...inputValues, [name]: value });
141+
};
142+
143+
const handleSubmit = async e => {
144+
e.preventDefault();
145+
146+
const submittedTransaction = await oktaAuth.idx.proceed({ step: transaction.nextStep.name, ...inputValues });
147+
console.log('Transaction:', submittedTransaction);
148+
setInputValues({});
149+
150+
if (submittedTransaction.status === IdxStatus.SUCCESS) {
151+
oktaAuth.tokenManager.setTokens(submittedTransaction.tokens);
152+
return;
69153
}
154+
155+
const newTransaction = await oktaAuth.idx.proceed({ step: submittedTransaction.nextStep.name });
156+
setTransaction(newTransaction);
157+
};
158+
159+
if (!transaction?.nextStep) {
160+
return null;
161+
}
162+
163+
const { inputs } = formTransformer(transaction.nextStep)({});
164+
165+
return (
166+
<form onSubmit={handleSubmit}>
167+
{inputs.map(({ label, name, type, required }) => (
168+
<label key={name}>
169+
{label}
170+
<input
171+
name={name}
172+
type={type}
173+
required={required}
174+
value={inputValues[name] || ''}
175+
onChange={handleChange}
176+
/>
177+
</label>
178+
))}
179+
<button type="submit">Sign in</button>
180+
</form>
181+
);
182+
}
183+
184+
function App() {
185+
const history = useHistory();
186+
const restoreOriginalUri = async (_oktaAuth, originalUri) => {
187+
history.replace(toRelativeUrl(originalUri || '/', window.location.origin));
70188
};
71189

72-
...
73-
```
190+
return (
191+
<Security oktaAuth={oktaAuth} restoreOriginalUri={restoreOriginalUri}>
192+
<SignInForm />
193+
</Security>
194+
);
195+
}
196+
197+
export default App;
198+
```

packages/@okta/vuepress-site/docs/guides/sign-in-to-spa-authjs/main/react/download-sample.md

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
### Download the sample React application
22

3-
To view a simple example of a React app, clone the Auth JS repository and follow the setup procedure:
3+
Clone the Auth JS repository and follow the setup procedure below to confirm your org and environment work end-to-end.
4+
5+
> **Note:** The generated sample still uses Auth JS's Legacy Mode internally, so its `App.jsx` and `GeneralForm.jsx` source don't match the Step Mode code shown later in this guide. Use the sample only to verify your app integration settings and environment; use this guide's own code snippets as the reference for how to build the sign-in logic.
46
57
#### Clone the Auth JS repository
68

@@ -26,7 +28,6 @@ Create and add a configuration file (`testenv`) to the `okta-auth-js` root folde
2628
```txt
2729
ISSUER=https://{yourOktaDomain}/oauth2/default
2830
CLIENT_ID={clientId}
29-
USE_INTERACTION_CODE=true
3031
```
3132

3233
#### Run the sample application
@@ -40,16 +41,17 @@ Navigate to the project folder and run the sample app. Click **Login** and sign
4041

4142
### Create a React app (optional)
4243

43-
If you don't have an existing React app, you can quickly create an app by using [Create React App](https://create-react-app.dev/):
44+
If you don't have an existing React app, you can quickly create one using [Vite](https://vite.dev/):
4445

4546
```bash
46-
npx create-react-app okta-app
47+
npm create vite@latest okta-app -- --template react
4748
```
4849

49-
Go into your root app directory to view the created files:
50+
Go into your app directory and install the base dependencies:
5051

5152
```bash
52-
cd okta-app
53+
cd okta-app
54+
npm install
5355
```
5456

5557
### Install dependencies
@@ -70,4 +72,15 @@ npm install @okta/okta-react@latest
7072
npm install react-router-dom@5
7173
```
7274

73-
> **Note:** The sample code in this use case requires `react-router-dom` version 5.x. Certain objects used in the sample code don't exist in `reactor-router-dom` version 6.x.
75+
> **Note:** The sample code in this use case requires `react-router-dom` version 5.x. Certain objects used in the sample code don't exist in `react-router-dom` version 6.x or later. Okta's own reference sample app is also still on `react-router-dom` version 5.x, so there's no version 6+ equivalent to switch to yet.
76+
77+
### Add environment variables
78+
79+
Vite only exposes environment variables to your app code when they're prefixed with `VITE_` and read through `import.meta.env`, unlike some other React tooling. Create a `.env` file in your app's root folder with your [app integration settings](#app-integration-settings):
80+
81+
```txt
82+
VITE_ISSUER=https://{yourOktaDomain}/oauth2/default
83+
VITE_CLIENT_ID={clientId}
84+
```
85+
86+
> **Note:** Add `.env` to your `.gitignore` file so you don't commit it to source control.

packages/@okta/vuepress-site/docs/guides/sign-in-to-spa-authjs/main/react/sign-in-form.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
1-
Build a sign-in page that captures both the username and password. As an example, from the test application, see the `index.js` file, which renders the simple sign-in for from the `formtransformer.js` file:
1+
Build a sign-in page that renders whichever fields the current step needs. This password-only flow asks for `username` first and `password` second. See the test application's `main.jsx` file, which renders the sign-in form using `formTransformer.js`:
22

33
```JavaScript
44
import React from 'react';
5-
import ReactDOM from 'react-dom';
5+
import { createRoot } from 'react-dom/client';
66
import { BrowserRouter as Router } from 'react-router-dom';
77
import App from './App';
88

9-
ReactDOM.render(
9+
const root = createRoot(document.getElementById('root'));
10+
root.render(
1011
<React.StrictMode>
1112
<Router>
1213
<App />
1314
</Router>
14-
</React.StrictMode>,
15-
document.getElementById('root')
15+
</React.StrictMode>
1616
);
1717
```
1818

19-
From the `formtransformer.js` file:
19+
From the `formTransformer.js` file:
2020

2121
```JavaScript
2222
const inputTransformer = nextStep => form => {

0 commit comments

Comments
 (0)