Express¶
Express is a highly popular Node.js web framework.
The Fief JavaScript client provides tools to help you integrate Fief authentication in your Express project. Let's see how to use them!
Install the client¶
API example¶
This is for you if...
- Your Express backend will work as a pure REST API.
- You have a separate frontend, like a JavaScript or mobile app, that'll take care of the OAuth2 flow.
In this first example, we won't implement routes to perform the OAuth2 authentication. The goal here is just to show you how to protect your API route with a Fief access token.
const fief = require('@fief/fief');
const fiefExpress = require('@fief/fief/express');
const express = require('express');
const PORT = 3000;
const fiefClient = new fief.Fief({ // (1)!
baseURL: 'https://fief.mydomain.com',
clientId: 'YOUR_CLIENT_ID',
clientSecret: 'YOUR_CLIENT_SECRET',
});
const fiefAuthMiddleware = fiefExpress.createMiddleware({ // (2)!
client: fiefClient, // (3)!
tokenGetter: fiefExpress.authorizationSchemeGetter(), // (4)!
});
const app = express();
app.get('/authenticated', fiefAuthMiddleware(), (req, res) => { // (5)!
res.json(req.accessTokenInfo); // (6)!
});
app.get('/authenticated-scope', fiefAuthMiddleware({ scope: ['openid', 'required_scope'] }), (req, res) => { // (7)!
res.json(req.accessTokenInfo);
});
app.get('/authenticated-permissions', fiefAuthMiddleware({ permissions: ['castles:read'] }), (req, res) => { // (8)!
res.json(req.accessTokenInfo);
});
app.listen(PORT, () => {
console.log(`Example app listening on port ${PORT}`)
});
-
Fief client instantiation
As we showed in the standard JavaScript section, we instantiate here a Fief client here with the base tenant URL and client credentials.
-
Middleware helper
The
fiefExpress
module exposes thecreateMiddleware
function which returns a proper middleware to use in your Express routes. -
The Fief client
The first mandatory parameter is the Fief client we just created above.
-
The token getter
The second mandatory parameter is a function retrieving the access token from an Express request.
You can create yours, as long as it follows the TokenGetter signature, or you can use the ones we provide.
Here, we use
authorizationSchemeGetter
, which will retrieve the token from anAuthorization
header with theBearer
scheme:Authorization: Bearer ACCESS_TOKEN
-
Use the middleware in your route
If you want to protect a route, all you need to do is to add your
fiefAuthMiddleware
instance as a middleware.When your handler is called, you're sure the user is authenticated with a valid session.
-
accessTokenInfo
is available inreq
In your route handler, you have access to the
accessTokenInfo
object inreq
.accessTokenInfo
is aFiefAccessTokenInfo
object containing the ID of the user, the list of allowed scopes and permissions and the raw access token. -
Check for scopes
fiefAuthMiddleware
accepts an optionalscope
parameter where you can list the scope required to access this route.If the access token doesn't have the required scope, the forbidden response is returned.
-
Check for permissions
fiefAuthMiddleware
accepts an optionalperrmissions
parameter where you can list the permissions required to access this route.If the access token doesn't have the required permissions, the forbidden response is returned.
Web application example¶
This is for you if...
- Your Express backend will render HTML pages.
- Your application is intended to be used in a browser.
Prerequisites
- Allow the following Redirect URI on your Fief Client:
http://localhost:3O00/auth-callback
The examples we showed previously are working well in a pure REST API context: a frontend, like interactive documentation, a JavaScript application or a mobile app will take care of the OAuth2 authentication flow to retrieve an access token before making request to your API.
Another common context is traditional web application, where the server takes care of generating HTML pages before returning it to the browser. In this case, we'll need some routes to redirect the user to the Fief login page if they're not authenticated and take care of storing the access token somewhere. This is what'll show in this example.
Besides, we'll usually need the basic information about the authenticated user, like its email or the values of the custom user fields. We'll see how we can use it.
Basically, here's what we'll do:
- This time, we'll expect the access token to be passed through a traditional cookie instead of an HTTP header. Cookies are very convenient when designing web apps because they are handled automatically by the browser.
- If the cookie is not present, we'll redirect the user to the Fief login page. Once again, the browser will help us a lot here since it'll automatically follow the redirection.
- Upon successful login, Fief will automatically redirect the user to the callback route. This callback route will take care of setting a new cookie containing the access token. It means that the access token will be safely stored in the browser memory.
- Finally, the user is redirected back to the protected route. The browser will automatically send the cookie containing the access token: our request is now authenticated!
const fief = require('@fief/fief');
const fiefExpress = require('@fief/fief/express');
const express = require('express');
const SESSION_COOKIE_NAME = "user_session" // (1)!
const PORT = 3000;
const REDIRECT_URI = `http://localhost:${PORT}/auth-callback`; // (2)!
class MemoryUserInfoCache { // (3)!
constructor() {
this.storage = {};
}
async get(id) {
const userinfo = this.storage[id];
if (userinfo) {
return userinfo;
}
return null;
}
async set(id, userinfo) {
this.storage[id] = userinfo;
}
async remove(id) {
this.storage[id] = undefined;
}
async clear() {
this.storage = {};
}
}
const userInfoCache = new MemoryUserInfoCache();
const fiefClient = new fief.Fief({ // (4)!
baseURL: 'https://fief.mydomain.com',
clientId: 'YOUR_CLIENT_ID',
clientSecret: 'YOUR_CLIENT_SECRET',
});
const unauthorizedResponse = async (req, res) => { // (5)!
const authURL = await fiefClient.getAuthURL({ redirectURI: REDIRECT_URI, scope: ['openid'] }); // (6)!
res.redirect(307, authURL); // (7)!
};
const fiefAuthMiddleware = fiefExpress.createMiddleware({
client: fiefClient,
tokenGetter: fiefExpress.cookieGetter(SESSION_COOKIE_NAME), // (8)!
unauthorizedResponse, // (9)!
userInfoCache, // (10)!
});
const app = express();
app.get('/auth-callback', async (req, res) => { // (11)!
const code = req.query['code'];
const [tokens, userinfo] = await fiefClient.authCallback(code, REDIRECT_URI); // (12)!
userInfoCache.set(userinfo.sub, userinfo); // (13)!
res.cookie( // (14)!
SESSION_COOKIE_NAME,
tokens.access_token,
{
maxAge: tokens.expires_in * 1000,
httpOnly: true, // (15)!
secure: false, // ❌ Set this to `true` in production (16)!
},
);
res.redirect('/protected');
});
app.get('/protected', fiefAuthMiddleware(), (req, res) => { // (17)!
res.send(`<h1>You are authenticated. Your user email is ${req.user.email}</h1>`) // (18)!
});
app.listen(PORT, () => {
console.log(`Example app listening on port ${PORT}`)
});
-
Define a session cookie name constant
As we said, we'll use a cookie to maintain the user session.
For convenience, we set its name in a constant.
-
Define a redirect URI constant
After the user has succesfully authenticated on Fief, our user will need to be redirected to our application so we can get the access token and set our session.
This constant is an absolute URL to our
/auth-callback
route we define below.In a production environment, it should corresponds to your actual domain.
-
Implement a user information cache
To make sure we don't call the Fief API every time we want the user data, we'll cache it in our application. It'll be way more performant!
To do this, we impement a class following the
IUserInfoCache
interface.In this example, we use very simple approach that will just store the data in memory. If your server is rebooted, the cache will be lost.
It can work quite well when starting, but you'll probably need more robust approaches in the long run, like writing to a Redis store. The good thing is that you'll only need to change this class when the time comes!
-
This doesn't change from the previous example
The
Fief
client is always at the heart of the integration 😉 -
Define a custom unauthorized response
By default, the Express integration will return a 401 response when the user is not authenticated.
In our case here, we want them to be redirected to Fief authentication page.
-
We generate an authorization URL on the Fief server
Thanks to the
getAuthURL
method on the Fief client, we can automatically generate the authorization URL on the Fief server. -
We build a redirect response
We redirect the user to the Fief authorization URL.
-
We use a cookie getter
Contrary to the previous examples, we expect the access token to be passed in a cookie. Thus, we use the provided
cookieGetter
function.Notice that we pass it the cookie name,
SESSION_COOKIE_NAME
, as parameter. -
We use our custom unauthorized response
We tell the middleware about our customer handler we defined above.
-
We use our user information cache
We tell the middleware about our user information cache, so it can use it to save and retrieve the data.
-
We implement an
/auth-callback
routeThis is the route that'll take care of exchanging the authorization code with a fresh access token and save it in a cookie.
-
We generate an access token
We finish the OAuth2 flow by exchanging the authorization code with a fresh access token.
-
We cache the user information
When a user has successfully authenticated, we do not only get the access token: we also get an ID token which already contains the user information.
Hence, we'll take this opportunity to store it in our cache! The ID token is automatically decoded by
fiefClient.authCallback
method.Thus, we just have to use our cache helper to store it!
-
We build a new cookie containing the access token
The response will contain a
Set-Cookie
header instructing the browser to save the access token in its memory. This method allows us to configure each properties of the cookie.You can read more about HTTP cookies on the MDN documentation.
-
Set the cookie as
HTTPOnly
For such sensitive values, it's strongly recommended to set the cookie as
HTTPOnly
. It means that it won't be possible to read its value from JavaScript, reducing potential attacks. -
Set the cookie as secure in production
For such sensitive values, it's strongly recommended to set the cookie as
Secure
. It tells the browser to send the cookie only on HTTPS (SSL) connection, reducing the risk of the access token to be stolen by a attacker between the client and the server.However, in a local environment, you usually don't serve your application with SSL. That's why we set it to
false
in this example. A common approach to handle this is to have an environment variable to control this parameter, so you can disable it in local and enable it in production. -
We implement a
/protected
routeNow, we can just use our
fiefAuthMiddleware
to protect our routes. -
user
object is available inreq
If the request is properly authenticated, the middleware will automatically add the
user
obkect toreq
.user
is aFiefUserInfo
object containing the user data. If it's not available in cache, it's automatically retrieved from the Fief API.
That's it! If you run this application and go to http://localhost:3000/protected, you'll be redirected to the Fief login page and experience the authentication flow before getting back to this route with a proper authentication cookie.