This guide will walk you through integrating authentication into a React app with Okta by performing these steps:
If you do not already have a Developer Edition Account, you can create one at https://developer.okta.com/signup/.
| Setting | Value |
|---|---|
| App Name | OpenId Connect App (must be unique) |
| Login redirect URIs | http://localhost:3000/callback |
| Logout redirect URIs | http://localhost:3000/login |
Note: CORS is automatically enabled for the granted login redirect URIs.
To quickly create a React app, install the create-react-app CLI:
$ npm install -g create-react-app
Now, create a new app:
$ create-react-app okta-app
This creates a new project named okta-app and installs all required dependencies.
A simple way to add authentication into a React app is using the Okta Auth JS library. We can install it via npm:
$ cd okta-app && npm install @okta/okta-auth-js --save
We’ll also need react-router-dom to manage our routes:
[okta-app] $ npm install react-router-dom --save
Users can sign in to your React app a number of different ways. To provide a fully featured and customizable login experience, the Okta Sign-In Widget is available to handle User Lifecycle operations, MFA, and more.
First, create src/auth.js as an authorization utility file and use it to bootstrap the required fields to login. This file will expose a withAuth method that makes it easy to create Higher-Order Components that include an auth prop:
Important: We’re using Okta’s organization authorization server to make setup easy, but it’s less flexible than a custom authorization server. Most SPAs send access tokens to access APIs. If you’re building an API that will need to accept access tokens, create an authorization server
// src/auth.js
import React from 'react';
import OktaAuth from '@okta/okta-auth-js';
class Auth {
constructor() {
this.login = this.login.bind(this);
this.logout = this.logout.bind(this);
this.redirect = this.redirect.bind(this);
this.isAuthenticated = this.isAuthenticated.bind(this);
this.handleAuthentication = this.handleAuthentication.bind(this);
oktaAuth = new OktaAuth({
url: 'https://{yourOktaDomain}.com',
clientId: '{clientId}',
issuer: 'https://{yourOktaDomain}.com',
redirectUri: 'http://localhost:3000/callback',
});
}
login(history) {
// Redirect to the login page
history.push('/login');
}
async logout(history) {
this.oktaAuth.tokenManager.clear();
await this.oktaAuth.signOut();
history.push('/');
}
redirect() {
// Launches the login redirect.
this.oktaAuth.token.getWithRedirect({
responseType: ['id_token', 'token'],
scopes: ['openid', 'email', 'profile']
});
}
isAuthenticated() {
// Checks if there is a current accessToken in the TokenManger.
return !!this.oktaAuth.tokenManager.get('accessToken');
}
async handleAuthentication() {
const tokens = await this.oktaAuth.token.parseFromUrl();
for (let token of tokens) {
if (token.idToken) {
this.oktaAuth.tokenManager.add('idToken', token);
} else if (token.accessToken) {
this.oktaAuth.tokenManager.add('accessToken', token);
}
}
}
}
// create a singleton
const auth = new Auth();
export const withAuth = WrappedComponent => props =>
<WrappedComponent auth={auth} {...props} />;
Some routes require authentication in order to render. Defining those routes is easy if we centralize our logic by creating a src/SecureRoute.js file:
// src/SecureRoute.js
import React from 'react';
import { Route, Redirect } from 'react-router';
import { withAuth } from './auth';
export default withAuth(({ auth, component: Component, ...rest }) => (
<Route {...rest} render={props => (
auth.isAuthenticated() ? (
<Component {...props}/>
) : (
<Redirect to={{
pathname: '/login',
state: { from: props.location }
}}/>
)
)}/>
));
Lets take a look at what routes are needed:
/: A default page to handle basic control of the app./protected: A route protected by SecureRoute./callback: Handle the response from Okta and store the returned tokens./login: Redirect to the org login page./First, create src/Home.js to provide links to navigate our app:
// src/Home.js
import React from 'react';
import { Link } from 'react-router-dom';
import { withRouter } from 'react-router';
import { withAuth } from './auth';
export default withAuth(withRouter(props => {
// Change the button that's displayed, based on our authentication status
const button = props.auth.isAuthenticated() ?
<button onClick={props.auth.logout.bind(null, props.history)}>Logout</button> :
<button onClick={props.auth.login.bind(null, props.history)}>Login</button>;
return (
<div>
<Link to='/'>Home</Link><br/>
<Link to='/protected'>Protected</Link><br/>
{button}
</div>
);
}));
/protectedThis route will only be visible to users with a valid accessToken.
Create a new component src/Protected.js:
// src/Protected.js
import React from 'react';
export default () => <h3>Protected</h3>;
/callbackIn order to handle the redirect back from Okta, we need to capture the token values from the URL. Use the /callback route to handle the logic of storing these tokens and redirecting back to the main page.
Create a new component src/Callback.js:
// src/Callback.js
import React, { Component } from 'react';
import { Redirect } from 'react-router-dom';
import { withAuth } from './auth';
export default withAuth(class Callback extends Component {
state = {
parsingTokens: false
}
componentWillMount() {
if (window.location.hash) {
this.setState({
parsingTokens: true
});
this.props.auth.handleAuthentication()
.then(() => {
this.setState({
parsingTokens: false
});
})
.catch(err => {
console.log('error logging in', err);
});
}
}
render() {
if (!this.state.parsingTokens) {
const pathname = localStorage.getItem('referrerPath') || '/';
return (
<Redirect to={pathname}/>
)
}
return null;
}
});
/loginThis route redirects if the user is already logged in. If the user is coming from a protected page, they’ll be redirected back to the page upon login.
Create a new component src/Login.js:
// src/Login.js
import React, { Component } from 'react';
import { Redirect } from 'react-router-dom';
import { withAuth } from './auth';
export default withAuth(class Login extends Component {
render() {
let from;
if (this.props.location && this.props.location.state) {
from = this.props.location.state.from;
} else {
from = { pathname: '/' };
}
if (this.props.auth.isAuthenticated()) {
return <Redirect to={from}/>;
}
localStorage.setItem('referrerPath', from.pathname);
this.props.auth.redirect();
return null;
}
});
Update src/App.js to include your project components and routes:
// src/App.js
import React, { Component } from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import SecureRoute from './SecureRoute';
import Home from './Home';
import Login from './Login';
import Callback from './Callback';
import Protected from './Protected';
class App extends Component {
render() {
return (
<Router>
<div>
<Route path='/' exact={true} component={Home}/>
<SecureRoute path='/protected' component={Protected}/>
<Route path='/login' component={Login}/>
<Route path='/callback' component={Callback}/>
</div>
</Router>
);
}
}
export default App;
Finally, start your app:
[okta-app] $ npm start
You have now successfully authenticated with Okta! Now what? With a user’s id_token, you have basic claims for the user’s identity. You can extend the set of claims by modifying the scopes to retrieve custom information about the user. This includes locale, address, groups, and more.
Have a question or see a bug? Post your question on Okta Developer Forums.