Skip to content
This repository was archived by the owner on Dec 3, 2021. It is now read-only.

Commit 4e398fb

Browse files
aguscha333adambrgmn
authored andcommitted
feat: Enhancement/custom token fetch fn (#29)
* fix access_token passthrough * changed fetch function * Custom fetch functions fix: #28, #30
1 parent ddbed5d commit 4e398fb

File tree

3 files changed

+104
-2
lines changed

3 files changed

+104
-2
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ redirected from the OAuth2-provider.
179179
| `location` | `{ search: string }` | no | - | Used to extract info from querystring [(read more below)](#location-and-querystring) |
180180
| `querystring` | `string` | no | - | Used to extract info from querystring [(read more below)](#location-and-querystring) |
181181
| `tokenFetchArgs` | `object` | no | `{}` | Used to fetch the token endpoint [(read more below)](#tokenfetchargs) |
182+
| `tokenFn` | `func` | no | `null` | Used to bypass default fetch function to fetch the token [(read more below)](#tokenfn) |
183+
182184

183185
#### Events
184186

@@ -274,6 +276,20 @@ merged and overriden with the `tokenFetchArgs`:
274276
{ method: 'GET', headers: { 'Content-Type': 'application/json' }}
275277
```
276278

279+
#### `tokenFn`
280+
281+
The prop `tokenFn` can be used to change how the token is fetched and received from
282+
the service. It's a way to bypass the default fetch all together and use your own.
283+
For example, if your `access-token` comes in the headers instead of the response body
284+
you will have to use your own fetch function to get those. Or perhaps you already
285+
have a custom built fetch function that communicates with your backend and you want
286+
to make use of it.
287+
288+
Your function will receive the `url` from the OauthReceiver, it takes the
289+
`tokenUrl` and builds it up with all the other needed parameters so you don't have to.
290+
It will also receive the `tokenFetchArgs` parameter just in case you need it. if you don't,
291+
just ignore it.
292+
277293
### `createOauthFlow`
278294

279295
```js

src/OauthReceiver/OauthReceiver.test.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,87 @@ describe('Component <OauthReceiver />', () => {
7777

7878
await waitForElement(() => getByTestId('done'));
7979

80+
expect(fetch2).toHaveBeenCalledWith(
81+
expect.stringContaining(props.tokenUrl),
82+
expect.objectContaining({
83+
method: expect.stringMatching('POST'),
84+
cache: expect.stringMatching('no-cache'),
85+
headers: expect.objectContaining({
86+
'Content-Type': expect.stringMatching(
87+
'application/x-www-form-urlencoded',
88+
),
89+
}),
90+
}),
91+
);
92+
});
93+
test('with custom fetch function, default args', async () => {
94+
const onAuthSuccess = jest.fn();
95+
const onAuthError = jest.fn();
96+
97+
const props = {
98+
tokenUrl: 'https://api.service.com/oauth2/token',
99+
tokenFn: fetch2,
100+
clientId: 'abc',
101+
clientSecret: 'abcdef',
102+
redirectUri: 'https://www.test.com/redirect',
103+
querystring: `?${qs.stringify({
104+
code: 'abc',
105+
state: JSON.stringify({ from: '/success' }),
106+
})}`,
107+
onAuthSuccess,
108+
onAuthError,
109+
};
110+
111+
const { getByTestId } = render(
112+
<OauthReceiver
113+
{...props}
114+
render={({ processing, state }) => (
115+
<div>
116+
{processing && <span data-testid="done">done</span>}
117+
<span data-testid="state">{state && state.from}</span>
118+
</div>
119+
)}
120+
/>,
121+
);
122+
123+
await waitForElement(() => getByTestId('done'));
124+
125+
expect(onAuthSuccess).toHaveBeenCalledTimes(1);
126+
expect(onAuthError).not.toHaveBeenCalled();
127+
128+
expect(getByTestId('state')).toHaveTextContent('/success');
129+
});
130+
131+
test('with custom token function and token uri fetch args', async () => {
132+
fetch2.mockClear();
133+
134+
const props = {
135+
tokenUrl: 'https://api.service.com/oauth2/token',
136+
tokenFn: fetch2,
137+
tokenFetchArgs: {
138+
cache: 'no-cache',
139+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
140+
},
141+
clientId: 'abc',
142+
clientSecret: 'abcdef',
143+
redirectUri: 'https://www.test.com/redirect',
144+
querystring: `?${qs.stringify({
145+
code: 'abc',
146+
state: JSON.stringify({ from: '/settings' }),
147+
})}`,
148+
};
149+
150+
const { getByTestId } = render(
151+
<OauthReceiver
152+
{...props}
153+
render={({ processing }) => (
154+
<div>{processing && <span data-testid="done">done</span>}</div>
155+
)}
156+
/>,
157+
);
158+
159+
await waitForElement(() => getByTestId('done'));
160+
80161
expect(fetch2).toHaveBeenCalledWith(
81162
expect.stringContaining(props.tokenUrl),
82163
expect.objectContaining({

src/OauthReceiver/index.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class OauthReceiver extends React.Component {
3232
clientSecret,
3333
redirectUri,
3434
args,
35+
tokenFn,
3536
onAuthSuccess,
3637
} = this.props;
3738

@@ -60,8 +61,10 @@ class OauthReceiver extends React.Component {
6061
const defaultFetchArgs = { method: 'POST', headers };
6162
const fetchArgs = Object.assign(defaultFetchArgs, tokenFetchArgs);
6263

63-
fetch2(url, fetchArgs)
64-
.then(response => {
64+
(typeof tokenFn === 'function' ?
65+
tokenFn(url, fetchArgs) :
66+
fetch2(url, fetchArgs)
67+
).then(response => {
6568
const accessToken = response.access_token;
6669

6770
if (typeof onAuthSuccess === 'function') {
@@ -140,6 +143,7 @@ OauthReceiver.propTypes = {
140143
),
141144
location: PropTypes.shape({ search: PropTypes.string.isRequired }),
142145
querystring: PropTypes.string,
146+
tokenFn: PropTypes.func,
143147
onAuthSuccess: PropTypes.func,
144148
onAuthError: PropTypes.func,
145149
render: PropTypes.func,
@@ -154,6 +158,7 @@ OauthReceiver.defaultProps = {
154158
args: {},
155159
location: null,
156160
querystring: null,
161+
tokenFn: null,
157162
onAuthSuccess: null,
158163
onAuthError: null,
159164
render: null,

0 commit comments

Comments
 (0)