react-auth-proxy
A turnkey solution for using authentication sessions in React built-in dev proxy
Philosophy
The package is intended to be lightweight and configurable to support most usual authentication flows.
From react official doc for custom dev proxy setup, developers need to create a setupProxy.js
file, that exports a function registering middlewares on an express-like
app.
The module registers middlewares to manage a testing 3rd party authentication flow:
- redirection to a login page
- storing user sessions
- setting end-user session cookie
- logout endpoint
Usage
Install
npm install react-auth-proxy
Create a setupProxy.js file with your business logic
As a developer, I want my webapp to authenticate to an API server via JWT in a bearer token.
setupProxy.js (in the same folder as index.js)
const reactAuthProxy = require('react-auth-proxy')
const jsonwebtoken = require('jsonwebtoken')
const config = {
auth: {
presetUser: { name: 'Carlo', role: 'account-manager' },
encode: user => 'bearer ' + jsonwebtoken.sign(user, 'secret'),
},
proxy: {
target: 'http://api-server:8080',
},
}
module.exports = reactAuthProxy(config)
Then restart the react dev server.
Config fields
Param Type | Default value | Description |
---|---|---|
auth.autoLogin bool | false |
When true, automatically login as the first presetUser |
auth.encode function | JSON.stringify |
takes a user as single param and encodes it into the header |
auth.headerName string | authorization |
Name of the header consumed by the backend |
auth.loginPath string | /.auth/login |
Path to get the login form HTML page |
auth.logoutPath string | /.auth/logout |
Path to trigger the current user's session deletion |
auth.presetUser object or array or objects |
{"name": "test"} |
The user data to provide to the backend for security checks (RBAC) |
auth.redirectParamName string | redirect |
Name of the query param carrying the url where to browse back after the login |
auth.serverPath string or array of strings | /local-server |
Path to reach the server APIs |
auth.successPath string | /.auth/success |
Path to get redirected for user creation after a successful login |
proxy object | {} |
Config forwarded to the http-proxy-middleware module |
proxy.callback function | undefined |
Called on the app, after all the registrations, used for custom registrations |
proxy.target string | http://localhost:8080 |
Dev instance of the server hosting the APIs |
sessionCookie.name string | session |
Name of the cookie stored on clend-side, holding the session ID value |
sessionCookie.maxAge int |
1000 * 60 * 60 * 24 * 7 = 1 week |
Number of milliseconds the cookie remains valid |
Module exports and exposed functions
Default export: the proxy registerer constructor.
Named export: the sessions
storage object.
Exposed data:
- the registerer merge of user config and default fields:
config
- the registerer core proxy middleware:
tradeSessionForJwt
- the registerer user extractor from the sessions, using the right cookie name:
getUser
.
Example
As a developer, I want my webapp to authenticate to an Azure App Service backend relying on Azure Active Directory.
The client only has access to a session ID stored in a cookie and an API Gateway trades it for a custom token on backend side.
setupProxy.js
const reactAuthProxy = require('react-auth-proxy')
const makeClaim = (typ, val) => ({typ, val})
function encodeMsUser(user) {
const payload = {
claims: [
makeClaim('name', user.name),
makeClaim('roles', user.role),
]
}
return Buffer.from(JSON.stringify(payload)).toString('base64')
}
const config = {
auth: {
presetUser: { name: 'Carlo', role: 'account-manager' },
headerName: 'x-ms-client-principal',
encode: encodeMsUser,
serverPath: '/api-server/v1',
},
proxy: {
target: 'http://api-server:8080',
},
sessionCookie: {
name: 'aad-session-id',
maxAge: 60000, // 1 minute
},
}
module.exports = reactAuthProxy(config)
Get your React webapp redirected to a login page when the session has expired
Many of webapp developers rely on axios
for API calls. Axios transparently follows the redirections during its calls, including the redirection to the login page that comes when the user session has expired.
To detect the login redirection, rely on the response content-type
:
import axios from 'axios'
export const instance = axios.create({
withCredentials: true,
baseURL: 'api-server/v1',
headers: {
'content-type': 'application/json',
},
})
const allowedRedirectDomains = [window.location.origin, "https://login.windows.net"]
instance.interceptors.response.use(
resp => {
const contentType = resp.headers['content-type']
if(contentType.includes('text/html')
&& allowedRedirectDomains.some(orig => resp.request.responseURL.startsWith(orig))) {
console.log('Refresh for new credendials', resp.request.responseURL)
const loginParams = new URLSearchParams()
loginParams.set("redirect", window.location)
const respUrl = new URL(resp.request.responseURL)
respUrl.search = "?" + loginParams // Override any API ref
window.location = respUrl
return
}
return resp.data
},
error => {
throw error
}
)
Any API call witnessing an expired session will result in the whole webapp browsing to the login page and provide a redirect
parameter to come back at the end of the authentication.
# Redirection after a logout
The client can set a query param called post_logout_redirect_uri
(same as Azure) when calling the logout so the proxy will redirect it to the given URL once the logout is done.
Flow sequence
sequenceDiagram
participant b as Browser
participant r as React Web App
participant p as React Auth Proxy
participant s as API server
autonumber
Note over b, s: No session yet
b->>r: Browse a webapp page
Note over r: No session cookie
r->>p: Initial API request<br/>for the API server
p->>p: Read the session cookie<br/>and check for user session
Note over p: No session found<br/>trigger a login
p->>r: Login HTML page
r->>b: Change the window.location<br/>to the login page
Note over b: Browse the login page
b->>p: The user selects the test user
p->>b: Change the window.location<br/>to the success URL
b->>p: Get on the success URL
Note over p: New session created
p->>b: Set the session cookie<br/>and redirect to the original webapp page
Note over b, s: Existing session
b->>r: Browse the original webapp page
Note over r: With a session cookie
r->>p: New API request<br/>for the API server
p->>p: Read the session cookie<br/>and check for user session
Note over p: A session found<br/>proxy to the API server
p->>s: API request<br/>+ encoded user data (JWT, ...)
Note over s: Read user data (RBAC, ...)
s->>r: API response