Skip to content

Commit

Permalink
chore: bump version to 5.0
Browse files Browse the repository at this point in the history
Decouple Security from react-router [OKTA-333775]

- Added required prop `restoreOriginalUri` to `Security`
- Updated readme with setting `restoreOriginalUri` callback for `react-router`
Fix dev-5.0 conflicts (#90)

Removed test on restoreOriginalUri
Note about basename duplication in README (#92)

* Note about basename duplication fix
* Added release status
* Added ToC
fix lint (no any)

OKTA-376992
<<<Jenkins Check-In of Tested SHA: 537cc50 for [email protected]>>>
Artifact: okta-react
  • Loading branch information
shuowu authored and eng-prod-CI-bot-okta committed Mar 11, 2021
1 parent 31043be commit 3bb576f
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 116 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 5.0.0

### Breaking Changes

- [#71](https://github.com/okta/okta-react/pull/71) Adds required prop `restoreOriginalUri` to `Security` that will override `restoreOriginalUri` callback of `oktaAuth`

# 4.1.1

### Bug Fixes
Expand Down
151 changes: 123 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@
[![npm version](https://img.shields.io/npm/v/@okta/okta-react.svg?style=flat-square)](https://www.npmjs.com/package/@okta/okta-react)
[![build status](https://img.shields.io/travis/okta/okta-react/master.svg?style=flat-square)](https://travis-ci.org/okta/okta-react)

* [Release status](#release-status)
* [Getting started](#getting-started)
* [Installation](#installation)
* [Usage](#usage)
* [Reference](#reference)
* [Migrating between versions](#migrating-between-versions)
* [Contributing](#contributing)
* [Development](#development)

Okta React SDK builds on top of the [Okta Auth SDK][].

This SDK is a toolkit to build Okta integration with many common "router" packages, such as [react-router][], [reach-router][], and others.
Expand Down Expand Up @@ -40,6 +49,21 @@ This library currently supports:
- [OAuth 2.0 Implicit Flow](https://tools.ietf.org/html/rfc6749#section-1.3.2)
- [OAuth 2.0 Authorization Code Flow](https://tools.ietf.org/html/rfc6749#section-1.3.1) with [Proof Key for Code Exchange (PKCE)](https://tools.ietf.org/html/rfc7636)

## Release Status

:heavy_check_mark: The current stable major version series is: `5.x`

| Version | Status |
| ------- | -------------------------------- |
| `5.x` | :heavy_check_mark: Stable |
| `4.x` | :warning: Retiring on 2021-12-09 |
| `3.x` | :warning: Retiring on 2021-08-20 |
| `2.x` | :x: Retired |
| `1.x` | :x: Retired |

The latest release can always be found on the [releases page][github-releases].


## Getting Started

- If you do not already have a **Developer Edition Account**, you can create one at [https://developer.okta.com/signup/](https://developer.okta.com/signup/).
Expand Down Expand Up @@ -121,40 +145,51 @@ This example defines 3 routes:
// src/App.js

import React, { Component } from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import { BrowserRouter as Router, Route, withRouter } from 'react-router-dom';
import { SecureRoute, Security, LoginCallback } from '@okta/okta-react';
import { OktaAuth } from '@okta/okta-auth-js';
import { OktaAuth, toRelativeUrl } from '@okta/okta-auth-js';
import Home from './Home';
import Protected from './Protected';

const oktaAuth = new OktaAuth({
issuer: 'https://{yourOktaDomain}.com/oauth2/default',
clientId: '{clientId}',
redirectUri: window.location.origin + '/login/callback'
});
class App extends Component {
constructor(props) {
super(props);
this.oktaAuth = new OktaAuth({
issuer: 'https://{yourOktaDomain}.com/oauth2/default',
clientId: '{clientId}',
redirectUri: window.location.origin + '/login/callback'
});
this.restoreOriginalUri = async (_oktaAuth, originalUri) => {
props.history.replace(toRelativeUrl(originalUri, window.location.origin));
};
}

render() {
return (
<Router>
<Security oktaAuth={oktaAuth}>
<Route path='/' exact={true} component={Home}/>
<SecureRoute path='/protected' component={Protected}/>
<Route path='/login/callback' component={LoginCallback} />
</Security>
</Router>
<Security oktaAuth={this.oktaAuth} restoreOriginalUri={this.restoreOriginalUri} >
<Route path='/' exact={true} component={Home} />
<SecureRoute path='/protected' component={Protected} />
<Route path='/login/callback' component={LoginCallback} />
</Security>
);
}
}

export default App;
const AppWithRouterAccess = withRouter(App);
export default class extends Component {
render() {
return (<Router><AppWithRouterAccess/></Router>);
}
}
```

#### Creating React Router Routes with function-based components

```jsx
import React from 'react';
import { SecureRoute, Security, LoginCallback } from '@okta/okta-react';
import { OktaAuth } from '@okta/okta-auth-js';
import { OktaAuth, toRelativeUrl } from '@okta/okta-auth-js';
import { useHistory } from 'react-router-dom';
import Home from './Home';
import Protected from './Protected';

Expand All @@ -163,15 +198,23 @@ const oktaAuth = new OktaAuth({
clientId: '{clientId}',
redirectUri: window.location.origin + '/login/callback'
});
const App = () => (
<Router>
<Security oktaAuth={oktaAuth}>
<Route path='/' exact={true} component={Home}/>
<SecureRoute path='/protected' component={Protected}/>
<Route path='/login/callback' component={LoginCallback} />
</Security>
</Router>
);

const App = () => {
const history = useHistory();
const restoreOriginalUri = async (_oktaAuth, originalUri) => {
history.replace(toRelativeUrl(originalUri, window.location.origin));
};

return (
<Router>
<Security oktaAuth={oktaAuth} restoreOriginalUri={restoreOriginalUri}>
<Route path='/' exact={true} component={Home} />
<SecureRoute path='/protected' component={Protected} />
<Route path='/login/callback' component={LoginCallback} />
</Security>
</Router>
);
};

export default App;
```
Expand Down Expand Up @@ -338,6 +381,10 @@ export default MessageList = () => {

*(required)* The pre-initialized [oktaAuth][Okta Auth SDK] instance. See [Configuration Reference](https://github.com/okta/okta-auth-js#configuration-reference) for details of how to initialize the instance.

#### restoreOriginalUri

*(required)* Callback function. Called to restore original URI during [oktaAuth.handleLoginRedirect()](https://github.com/okta/okta-auth-js#handleloginredirecttokens) is called. Will override [restoreOriginalUri option of oktaAuth](https://github.com/okta/okta-auth-js#restoreoriginaluri)

#### onAuthRequired

*(optional)* Callback function. Called when authentication is required. If this is not supplied, `okta-react` redirects to Okta. This callback will receive [oktaAuth][Okta Auth SDK] instance as the first function parameter. This is triggered when a [SecureRoute](#secureroute) is accessed without authentication. A common use case for this callback is to redirect users to a custom login route when authentication is required for a [SecureRoute](#secureroute).
Expand All @@ -346,7 +393,7 @@ export default MessageList = () => {

```jsx
import { useHistory } from 'react-router-dom';
import { OktaAuth } from '@okta/okta-auth-js';
import { OktaAuth, toRelativeUrl } from '@okta/okta-auth-js';

const oktaAuth = new OktaAuth({
issuer: 'https://{yourOktaDomain}.com/oauth2/default',
Expand All @@ -363,10 +410,15 @@ export default App = () => {
history.push('/login');
};

const restoreOriginalUri = async (_oktaAuth, originalUri) => {
history.replace(toRelativeUrl(originalUri, window.location.origin));
};

return (
<Security
oktaAuth={oktaAuth}
onAuthRequired={customAuthHandler}
restoreOriginalUri={restoreOriginalUri}
>
<Route path='/login' component={CustomLoginComponent}>
{/* some routes here */}
Expand Down Expand Up @@ -395,8 +447,8 @@ class App extends Component {
render() {
return (
<Router>
<Security oktaAuth={oktaAuth}>
<Route path='/' exact={true} component={Home}/>
<Security oktaAuth={oktaAuth} restoreOriginalUri={restoreOriginalUri}>
<Route path='/' exact={true} component={Home} />
<Route path='/login/callback' component={LoginCallback} />
</Security>
</Router>
Expand Down Expand Up @@ -452,6 +504,49 @@ export default MyComponent = () => {

## Migrating between versions

### Migrating from 4.x to 5.x

From version 5.0, the Security component explicitly requires prop [restoreOriginalUri](#restoreoriginaluri) to decouple from `react-router`.
Example of implementation of this callback for `react-router`:

```jsx
import { Security } from '@okta/okta-react';
import { useHistory } from 'react-router-dom';
import { OktaAuth, toRelativeUrl } from '@okta/okta-auth-js';

const oktaAuth = new OktaAuth({
issuer: 'https://{yourOktaDomain}.com/oauth2/default',
clientId: '{clientId}',
redirectUri: window.location.origin + '/login/callback'
});

export default App = () => {
const history = useHistory();
const restoreOriginalUri = async (_oktaAuth, originalUri) => {
history.replace(toRelativeUrl(originalUri, window.location.origin));
};

return (
<Security
oktaAuth={oktaAuth}
restoreOriginalUri={restoreOriginalUri}
>
{/* some routes here */}
</Security>
);
};
```

**Note:** If you use `basename` prop for `<BrowserRouter>`, use this implementation to fix `basename` duplication problem:
```jsx
import { toRelativeUrl } from '@okta/okta-auth-js';
const restoreOriginalUri = async (_oktaAuth, originalUri) => {
const basepath = history.createHref({});
const originalUriWithoutBasepath = originalUri.replace(basepath, '/');
history.replace(toRelativeUrl(originalUriWithoutBasepath, window.location.origin));
};
```

### Migrating from 3.x to 4.x

#### Updating the Security component
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@okta/okta-react",
"version": "4.2.0",
"version": "5.0.0",
"description": "React support for Okta",
"private": true,
"scripts": {
Expand Down
2 changes: 2 additions & 0 deletions src/OktaContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { AuthState, OktaAuth } from '@okta/okta-auth-js';

export type OnAuthRequiredFunction = (oktaAuth: OktaAuth) => Promise<void> | void;

export type RestoreOriginalUriFunction = (oktaAuth: OktaAuth, originalUri: string) => Promise<void> | void;

export interface IOktaContext {
oktaAuth: OktaAuth;
authState: AuthState;
Expand Down
34 changes: 19 additions & 15 deletions src/Security.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,21 @@
*/

import * as React from 'react';
import { useHistory } from 'react-router-dom';
import { toRelativeUrl, AuthSdkError, OktaAuth } from '@okta/okta-auth-js';
import OktaContext, { OnAuthRequiredFunction } from './OktaContext';
import { AuthSdkError, OktaAuth } from '@okta/okta-auth-js';
import OktaContext, { OnAuthRequiredFunction, RestoreOriginalUriFunction } from './OktaContext';
import OktaError from './OktaError';

const Security: React.FC<{
oktaAuth: OktaAuth,
oktaAuth: OktaAuth,
restoreOriginalUri: RestoreOriginalUriFunction,
onAuthRequired?: OnAuthRequiredFunction,
children?: React.ReactNode
} & React.HTMLAttributes<HTMLDivElement>> = ({
oktaAuth,
oktaAuth,
restoreOriginalUri,
onAuthRequired,
children
children
}) => {
const history = useHistory();
const [authState, setAuthState] = React.useState(() => {
if (!oktaAuth) {
return {
Expand All @@ -39,18 +39,17 @@ const Security: React.FC<{
});

React.useEffect(() => {
if (!oktaAuth) {
if (!oktaAuth || !restoreOriginalUri) {
return;
}

// Add default restoreOriginalUri callback
if (!oktaAuth.options.restoreOriginalUri) {
oktaAuth.options.restoreOriginalUri = async (_, originalUri) => {
const basepath = history.createHref({});
const originalUriWithoutBasepath = originalUri.replace(basepath, '/');
history.replace(toRelativeUrl(originalUriWithoutBasepath, window.location.origin));
};
if (oktaAuth.options.restoreOriginalUri && restoreOriginalUri) {
console.warn('Two custom restoreOriginalUri callbacks are detected. The one from the OktaAuth configuration will be overridden by the provided restoreOriginalUri prop from the Security component.');
}
oktaAuth.options.restoreOriginalUri = async (oktaAuth: unknown, originalUri: string) => {
restoreOriginalUri(oktaAuth as OktaAuth, originalUri);
};

// Add okta-react userAgent
oktaAuth.userAgent = `${process.env.PACKAGE_NAME}/${process.env.PACKAGE_VERSION} ${oktaAuth.userAgent}`;
Expand All @@ -66,13 +65,18 @@ const Security: React.FC<{
}

return () => oktaAuth.authStateManager.unsubscribe();
}, [oktaAuth, history]);
}, [oktaAuth, restoreOriginalUri]);

if (!oktaAuth) {
const err = new AuthSdkError('No oktaAuth instance passed to Security Component.');
return <OktaError error={err} />;
}

if (!restoreOriginalUri) {
const err = new AuthSdkError('No restoreOriginalUri callback passed to Security Component.');
return <OktaError error={err} />;
}

return (
<OktaContext.Provider value={{
oktaAuth,
Expand Down
7 changes: 6 additions & 1 deletion test/e2e/harness/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import * as React from 'react';
import { Route, Switch, useHistory } from 'react-router-dom';
import { OktaAuth } from '@okta/okta-auth-js';
import { OktaAuth, toRelativeUrl } from '@okta/okta-auth-js';
import { Security, LoginCallback, SecureRoute } from '@okta/okta-react';
import Home from './Home';
import Protected from './Protected';
Expand All @@ -29,11 +29,16 @@ const App: React.FC<{
history.push('/login');
};

const restoreOriginalUri = async (_oktaAuth: OktaAuth, originalUri: string) => {
history.replace(toRelativeUrl(originalUri, window.location.origin));
};

return (
<React.StrictMode>
<Security
oktaAuth={oktaAuth}
onAuthRequired={customLogin ? onAuthRequired : undefined}
restoreOriginalUri={restoreOriginalUri}
>
<Switch>
<Route path='/login' component={CustomLogin}/>
Expand Down
8 changes: 7 additions & 1 deletion test/jest/loginCallback.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ describe('<LoginCallback />', () => {
let oktaAuth;
let authState;
let mockProps;
const restoreOriginalUri = async (_, url) => {
location.href = url;
};
beforeEach(() => {
authState = {
isPending: true
Expand All @@ -33,7 +36,10 @@ describe('<LoginCallback />', () => {
isLoginRedirect: jest.fn().mockImplementation(() => false),
handleLoginRedirect: jest.fn()
};
mockProps = { oktaAuth };
mockProps = {
oktaAuth,
restoreOriginalUri
};
});

it('renders the component', () => {
Expand Down
Loading

0 comments on commit 3bb576f

Please sign in to comment.