Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

merge react oauth2 hook #552

Open
wants to merge 55 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
66a82a0
init [email protected]
Jun 13, 2019
125a91c
working :)
Jun 16, 2019
edc4023
missing these
Jun 16, 2019
f9235f2
update jsdoc
Jun 16, 2019
e344e29
v1.0.1
Jun 16, 2019
d0b771d
more complete example
Jun 16, 2019
45d4fa5
still broken on github tho lol
Jun 16, 2019
8614a40
cool new readme!
Jun 17, 2019
1bba8d3
v1.0.2
Jun 17, 2019
7ae7b32
fix image link
Jun 17, 2019
cddb5c3
v1.0.3
Jun 17, 2019
2f1c80c
init [email protected]
Jun 21, 2019
1a4b48a
typescript
Jun 21, 2019
443d1a0
wow a lot to fix...
Jun 21, 2019
9aad1a9
somewhat good
Jun 21, 2019
6ed3dac
seems good?
Jun 21, 2019
8aa14d1
seems good!
Jun 21, 2019
f05e04d
v1.0.4
Jun 21, 2019
7c89916
weird and buggy but ok
Jun 21, 2019
d676dee
v1.0.5
Jun 21, 2019
4eb74ee
v1.0.6
Jun 21, 2019
87996f9
cool!!
Jun 21, 2019
e6bcf66
fix link to example on npm
Jun 21, 2019
075ba27
fix vestigial typo
Jun 21, 2019
bf30e2f
oops forgot to commit these
Jun 21, 2019
04013a3
upgrade deps
Jun 24, 2019
60f1d37
apparently the readme got deleted before
Jun 24, 2019
7a306d3
pick up fix in typedoc-plugin-markdown 2.0.7 https://github.com/tgrey…
Jun 29, 2019
7028270
compile for es6 and remove peerDeps issue for massive size reduction
Jun 30, 2019
e142d9b
v1.0.11
Jun 30, 2019
b766b03
fix another peerDeps issue...
Jun 30, 2019
7a4e508
v1.0.12
Jun 30, 2019
87c5f66
Fix decoding of url params
noahm Dec 5, 2019
9854b72
Fix error boundary to stop after one error
noahm Dec 5, 2019
a980f11
Allow overiding "please wait" by passing children
noahm Dec 5, 2019
30fd04e
General types cleanup
noahm Dec 5, 2019
3e61b41
Merge pull request #1 from noahm/fix-param-decoding
Zemnmez Dec 5, 2019
0ccd87c
prepare to merge react-oauth2-hook into monorepo
Zemnmez Jul 13, 2022
bc510d2
Merge commit '0ccd87c0' into merge-react-oauth2-hook
Zemnmez Jul 13, 2022
2f83e2a
fixes
Zemnmez Jul 13, 2022
b88a604
fixes and also progress bar for eslint
Zemnmez Jul 13, 2022
1d66817
fixes
Zemnmez Jul 13, 2022
850a3fd
Merge branch 'main' into merge-react-oauth2-hook
Zemnmez Jul 24, 2022
92a2bcd
upgrade to newer react where children must be explicit
Zemnmez Jul 24, 2022
b8181a0
react-oauth2-hook: some changes
Zemnmez Jul 24, 2022
390248c
react-oauth2-hook: a bit more refactoring
Zemnmez Jul 24, 2022
fa0afeb
forgot this
Zemnmez Jul 25, 2022
4d76ee8
Merge branch 'main' into merge-react-oauth2-hook
Zemnmez Aug 24, 2022
45d7b41
Merge branch 'main' into merge-react-oauth2-hook
Zemnmez Aug 24, 2022
e7cd69d
remove these
Zemnmez Aug 24, 2022
f5e9023
remove this too
Zemnmez Aug 24, 2022
3c39c5b
Merge branch 'merge-react-oauth2-hook' of github.com:Zemnmez/monorepo…
Zemnmez Aug 24, 2022
c8e9fd6
move a little closer to compiling
Zemnmez Aug 24, 2022
60a8e17
try and describe oauth2 a bit better
Zemnmez Sep 22, 2022
1513fe7
idk
Zemnmez Sep 23, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"parser": "babel-eslint",
"extends": [
"standard",
"standard-react"
],
"env": {
"es6": true
},
"plugins": [
"react"
],
"parserOptions": {
"sourceType": "module"
},
"rules": {
// don't force es6 functions to include space before paren
"space-before-function-paren": 0,

// allow specifying true explicitly for boolean props
"react/jsx-boolean-value": 0
}
}
2 changes: 1 addition & 1 deletion bzl/fix.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ echo Fixing Go files... 1>&2
$FIX_GO -s -w .
echo Fixing Typescript, Javascript, json files... 1>&2
# this breaks all the time and yarn run @npm//eslint/bin:eslint fixes it. God only knows why
$FIX_JS --fix --ignore-pattern 'project/ck3/base_game/*' --ignore-path .gitignore "$BUILD_WORKSPACE_DIRECTORY"'/**/*.ts' "$BUILD_WORKSPACE_DIRECTORY"'/**/*.js' "$BUILD_WORKSPACE_DIRECTORY"'/**/*.tsx' "$BUILD_WORKSPACE_DIRECTORY"'/**/*.json') || true # ignore failures. it fails often
$FIX_JS --rule 'file-progress/activate: 1' --plugin file-progress --fix --ignore-pattern 'project/ck3/base_game/*' --ignore-path .gitignore "$BUILD_WORKSPACE_DIRECTORY"'/**/*.ts' "$BUILD_WORKSPACE_DIRECTORY"'/**/*.js' "$BUILD_WORKSPACE_DIRECTORY"'/**/*.tsx' "$BUILD_WORKSPACE_DIRECTORY"'/**/*.json') || true # ignore failures. it fails often
echo Fixing bazel files... 1>&2
$FIX_BAZEL --lint=fix -r $INIT_CWD
3 changes: 1 addition & 2 deletions js/npm/yarn/lock/testing/package.template.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "test123",
"blah": 1
"name": "test123"
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"eslint": "^8.22.0",
"eslint-config-next": "12.2.5",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-file-progress": "^1.3.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.30.1",
"grunt-cli": "^1.4.3",
Expand All @@ -71,6 +72,7 @@
"react-dom": "^18.2.0",
"react-router": "^6.3.0",
"react-router-dom": "^6.3.0",
"react-storage-hooks": "^4.0.1",
"react-spring": "^9.5.2",
"regenerator-runtime": "^0.13.9",
"sharp": "^0.30.7",
Expand Down
22 changes: 22 additions & 0 deletions ts/react/hook/oauth2/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
load("//ts:rules.bzl", "ts_project")

package(default_visibility = ["//:__subpackages__"])

ts_project(
name = "oauth2",
srcs = glob([
"**/*.ts",
"**/*.tsx",
"**/*.json",
"**/*.js",
"**/*.jsx",
]),
resolve_json_module = True,
deps = [
"@npm//@types/react",
"@npm//react",
"@npm//react-storage-hooks",
"@npm//immutable",
"@npm//@types/jest"
],
)
290 changes: 290 additions & 0 deletions ts/react/hook/oauth2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
> **[react-oauth2-hook](README.md)**

[Globals]() / [react-oauth2-hook](README.md) /

**`requires`** immutable

**`requires`** prop-types

**`requires`** react

**`requires`** react-dom

**`requires`** react-storage-hook

**`summary`** Retrieve OAuth2 implicit grant tokens purely on the client without destroying application state.

**`version`** 1.0.11

**`author`** zemnmez

**`copyright`** zemnmez 2019

**`license`** MIT
## Installation

```bash
yarn add react-oauth2-hook
```
## Overview
This package provides an entirely client-side flow to get OAuth2 implicit grant tokens.
It's implemented as a react hook, [useOAuth2Token](README.md#const-useoauth2token), with a fairly simple API
and a react component, [OAuthCallback](README.md#const-oauthcallback) which should be mounted at the
OAuth callback endpoint.

Take a look at the [Example](#example) for usage information.

## Security Considerations
OAuth 2 is a very sensitive protocol. I've done my best to provide good security
guarantees with this package.

I assume that your application follows reasonable best practices like using `X-Frame-Options`
to prevent clickjacking based attacks.

### State
The State token prevents an attacker from forcing a user to sign in as the attacker's
account using a kind of CSRF. Here, I am cautious against multiple types of attacks.

My state token is not signed, it's a completely static concatenation of some entropy
generated by webcrypto and a key, composed of `JSON.stringify({ authUrl, clientID, scopes })`.
When the callback is recieved by [OAuthCallback](README.md#const-oauthcallback), it is compared strictly
to the stored value, and otherwise rejected.

This prevents both attacks where an attacker would try to submit a token to the user's
browser without their consent, and attacks where a malicious OAuth server would
(re)use the n-once to authenticate a callback from a different server.

### Timing attacks

The state token is *not* compared using a fixed-time string comparison.
Where typically, this would lead to an attacker being able to use a lot of time
and statistics to side-channel out the state token, this
should be irrelevant in this configuration, this should be extremely difficult
to pull off accurately as any timing information would be inaccessible or heavily
diluted.

## Refresh tokens
This library in-and-of-itself does not acquire long lived refresh tokens. Though
some OAuth servers allow implicit clients to acquire refresh tokens without an
OAuth secret, this isn't part of the OAuth standard. Instead, consider
simply triggering the authorize flow when the token expires -- if the user
is still authorized, the window should almost immediately close. Otherwise,
you can use any special APIs that would let you do this, or skip this library
entirely and try PKCE.
## Example

**`example`**

```javascript
import React from 'react'
import { BrowserRouter as Router, Switch } from 'react-router-dom'
import { useOAuth2Token, OAuthCallback } from 'react-oauth2-hook'

// in this example, we get a Spotify OAuth
// token and use it to show a user's saved
// tracks.

export default () => <Router>
<Switch>
<Route path="/callback" component={OAuthCallback}/>
<Route component={SavedTracks}/>
</Switch>
</Router>

const SavedTracks = () => {
const [token, getToken] = useOAuth2Token({
authorizeUrl: "https://accounts.spotify.com/authorize",
scope: ["user-library-read"],
clientID: "bd9844d654f242f782509461bdba068c",
redirectUri: document.location.href+"/callback"
})

const [tracks, setTracks] = React.useState();
const [error, setError] = React.useState();

// query spotify when we get a token
React.useEffect(() => {
fetch(
'https://api.spotify.com/v1/me/tracks?limit=50'
).then(response => response.json()).then(
data => setTracks(data)
).catch(error => setError(error))
}, [token])

return <div>
{error && `Error occurred: ${error}`}
{(!token || !savedTracks) && <div
onClick={getToken}>
login with Spotify
</div>}
{savedTracks && `
Your Saved Tracks: ${JSON.stringify(savedTracks)}
`}
</div>
}
```

### Index

#### Type aliases

* [OAuthToken](README.md#oauthtoken)
* [getToken](README.md#gettoken)
* [setToken](README.md#settoken)

#### Variables

* [ErrIncorrectStateToken](README.md#const-errincorrectstatetoken)
* [ErrNoAccessToken](README.md#const-errnoaccesstoken)

#### Functions

* [OAuthCallback](README.md#const-oauthcallback)
* [useOAuth2Token](README.md#const-useoauth2token)

## Type aliases

### OAuthToken

Ƭ **OAuthToken**: *string*

*Defined in [index.tsx:157](https://github.com/Zemnmez/react-oauth2-hook/blob/e142d9b/src/index.tsx#L157)*

OAuthToken represents an OAuth2 implicit grant token.

___

### getToken

Ƭ **getToken**: *function*

*Defined in [index.tsx:163](https://github.com/Zemnmez/react-oauth2-hook/blob/e142d9b/src/index.tsx#L163)*

getToken is returned by [useOAuth2Token](README.md#const-useoauth2token).
When called, it prompts the user to authorize.

#### Type declaration:

▸ (): *void*

___

### setToken

Ƭ **setToken**: *function*

*Defined in [index.tsx:171](https://github.com/Zemnmez/react-oauth2-hook/blob/e142d9b/src/index.tsx#L171)*

setToken is returned by [useOAuth2Token](README.md#const-useoauth2token).
When called, it overwrites any stored OAuth token.
`setToken(undefined)` can be used to synchronously
invalidate all instances of this OAuth token.

#### Type declaration:

▸ (`newValue`: *[OAuthToken](README.md#oauthtoken) | undefined*): *void*

**Parameters:**

Name | Type |
------ | ------ |
`newValue` | [OAuthToken](README.md#oauthtoken) \| undefined |

## Variables

### `Const` ErrIncorrectStateToken

• **ErrIncorrectStateToken**: *`Error`* = new Error('incorrect state token')

*Defined in [index.tsx:210](https://github.com/Zemnmez/react-oauth2-hook/blob/e142d9b/src/index.tsx#L210)*

This error is thrown by the [OAuthCallback](README.md#const-oauthcallback)
when the state token recieved is incorrect or does not exist.

___

### `Const` ErrNoAccessToken

• **ErrNoAccessToken**: *`Error`* = new Error('no access_token')

*Defined in [index.tsx:216](https://github.com/Zemnmez/react-oauth2-hook/blob/e142d9b/src/index.tsx#L216)*

This error is thrown by the [OAuthCallback](README.md#const-oauthcallback)
if no access_token is recieved.

## Functions

### `Const` OAuthCallback

▸ **OAuthCallback**(`__namedParameters`: *object*): *`Element`*

*Defined in [index.tsx:274](https://github.com/Zemnmez/react-oauth2-hook/blob/e142d9b/src/index.tsx#L274)*

OAuthCallback is a React component that handles the callback
step of the OAuth2 protocol.

OAuth2Callback is expected to be rendered on the url corresponding
to your redirect_uri.

By default, this component will deal with errors by closing the window,
via its own React error boundary. Pass `{ errorBoundary: false }`
to handle this functionality yourself.

**Parameters:**

▪ **__namedParameters**: *object*

Name | Type | Default | Description |
------ | ------ | ------ | ------ |
`errorBoundary` | boolean | true | When set to true, errors are thrown instead of just closing the window. |

**Returns:** *`Element`*

___

### `Const` useOAuth2Token

▸ **useOAuth2Token**(`__namedParameters`: *object*): *[[OAuthToken](README.md#oauthtoken) | undefined, [getToken](README.md#gettoken), [setToken](README.md#settoken)]*

*Defined in [index.tsx:95](https://github.com/Zemnmez/react-oauth2-hook/blob/e142d9b/src/index.tsx#L95)*

useOAuth2Token is a React hook providing an OAuth2 implicit grant token.

When useToken is called, it will attempt to retrieve an existing
token by the criteria of `{ authorizeUrl, scopes, clientID }`.
If a token by these specifications does not exist, the first
item in the returned array will be `undefined`.

If the user wishes to retrieve a new token, they can call `getToken()`,
a function returned by the second parameter. When called, the function
will open a window for the user to confirm the OAuth grant, and
pass it back as expected via the hook.

The OAuth token must be passed to a static endpoint. As
such, the `callbackUrl` must be passed with this endpoint.
The `callbackUrl` should render the [OAuthCallback](README.md#const-oauthcallback) component,
which will securely verify the token and pass it back,
before closing the window.

All instances of this hook requesting the same token and scopes
from the same place are synchronised. In concrete terms,
if you have many components waiting for a Facebook OAuth token
to make a call, they will all immediately update when any component
gets a token.

Finally, in advanced cases the user can manually overwrite any
stored token by capturing and calling the third item in
the reponse array with the new value.

**Parameters:**

▪ **__namedParameters**: *object*

Name | Type | Default | Description |
------ | ------ | ------ | ------ |
`authorizeUrl` | string | - | The OAuth authorize URL to retrieve the token from. |
`clientID` | string | - | The OAuth `client_id` corresponding to the requesting client. |
`redirectUri` | string | - | The OAuth `redirect_uri` callback. |
`scope` | string[] | [] | The OAuth scopes to request. |

**Returns:** *[[OAuthToken](README.md#oauthtoken) | undefined, [getToken](README.md#gettoken), [setToken](README.md#settoken)]*
Loading