Authorization: API Routes
Setting up API Routes
Before we do anything else we need to set up several API routes to handle various interactions with the WithWine API. We want to leverage Next.js server-side functionality to transmit and recieve data securely between your webserver and WithWine's API.
For the Authorization flow to operate successfully we'll need six API routes
defined in ./pages/api/withwine/
:
/api/withwine/login
/api/withwine/login-callback
/api/withwine/logout
/api/withwine/logout-callback
/api/withwine/token-refresh
/api/withwine/user
Each of these routes will carry out an important role in the WithWine Authorization Flow and are required for setps within the flow, or to get a users current Authorization state. Right now we have two options for how we'd like to handle interactions with the Login, Register and Logout links or buttons.
- Use a
fetch
to send the required requests to the API's directly with no redirects on intermediate pages - Create a redirect or rewrite for each action, so that from the users perspective they are just clicking links to pages residing on your website while under the hood we're redirecting them to the appropriate API's
The first (and preferred) option is the simplest to implement and has no dependancy on redirecting at the page level, while the second option gives you some additional flexibility in the case where you may already have login and logout pages and you are looking to maintain existing website navigation patterns.
In either case you will need the five API routes outlined above, so let's take a look at these routes now in detail.
At this point it's important to note that the two callbacks /login-callback
and /logout-callback
will need to match the values defined in your website
settings on WithWine. These are defined under Custom Credentials and
Configuration in the website settings in the WithWine Admin interface.
If these values do not macth those supplied by the API you'll recieve an error as outlined in the Troubleshooting guide.
If you can't match these routes directly it's recommended to use a redirect as outlined here.
/api/withwine/login
The WithWine login API route redirects the user to the WithWine authorization
page, the destination is generated by the getAuthorizationPath
function. The
user will then complete the login process and be returned to your website.
- TypeScript
- JavaScript
import { NextApiRequest, NextApiResponse } from 'next';
import { getAuthorizationPath } from '@withwine/sdk';
/**
* The WithWine login route redirects the user to the WithWine authorization
* page, the destination is generated by the `getAuthorizationPath` function.
* Once logged in the user is returned to the `/LoginCallback` route.
*/
export default async function Login(req: NextApiRequest, res: NextApiResponse) {
const referer = req?.headers.referer;
const loginInfo = req?.query.loginInfo as string;
const destination = getAuthorizationPath({
siteReturnUrl: referer,
loginInfo,
});
res.redirect(307, destination);
}
import { getAuthorizationPath } from '@withwine/sdk';
/**
* The WithWine login route redirects the user to the WithWine authorization
* page, the destination is generated by the `getAuthorizationPath` function.
* Once logged in the user is returned to the `/LoginCallback` route.
*/
export default async function Login(req, res) {
const referer = req?.headers.referer;
const loginInfo = req?.query.loginInfo;
const destination = getAuthorizationPath({
siteReturnUrl: referer,
loginInfo,
});
res.redirect(307, destination);
}
You will notice we're checking the req.query.loginInfo
in the code above, this
is mapped to the :params*
we passed in the /user/login/:params*
redirect
(if you are using redirects), or in the
/api/withwine/login
API route. This parameter defaults to login
but can be
supplied to the getAuthorizationPath
function as register
to direct the user
to the WithWine Account Registration step if desired.
# API Route
/api/withwine/login?loginInfo=register
# With redirects
/user/login?loginInfo=register
/api/withwine/login-callback
The Login Callback API route handles the POST
response from the WithWine
Authorization page and then redirects the user back to the original referring
page. The getTokenUsingAuthorizationCode
function handles the response and
sets the required cookies.
- TypeScript
- JavaScript
import { NextApiRequest, NextApiResponse } from 'next';
import { getTokenUsingAuthorizationCode } from '@withwine/sdk';
import { withWithWineSessionApiRoute } from '@withwine/sdk/next';
/**
* This LoginCallback API route handles the POST response from the WithWine
* Authorization login/register page and then redirects the user back to the
* original referring page.
* The getTokenUsingAuthorizationCode function handles the response and sets the
* required cookies.
*/
async function loginCallback(req: NextApiRequest, res: NextApiResponse) {
await req.body;
if (req?.body?.code && req?.body?.state) {
const { code, state } = req.body;
const result = await getTokenUsingAuthorizationCode({
code,
state,
req,
});
if (result) {
res.redirect(307, result.siteRedirectUrl);
}
} else {
req.session.destroy();
res.redirect(307, '/');
}
}
export default withWithWineSessionApiRoute(loginCallback);
import { getTokenUsingAuthorizationCode } from '@withwine/sdk';
import { withWithWineSessionApiRoute } from '@withwine/sdk/next';
/**
* This LoginCallback API route handles the POST response from the WithWine
* Authorization login/register page and then redirects the user back to the
* original referring page.
* The getTokenUsingAuthorizationCode function handles the response and sets the
* required cookies.
*/
async function loginCallback(req, res) {
await req.body;
if (req?.body?.code && req?.body?.state) {
const { code, state } = req.body;
const result = await getTokenUsingAuthorizationCode({
code,
state,
req,
});
if (result) {
res.redirect(307, result.siteRedirectUrl);
}
} else {
req.session.destroy();
res.redirect(307, '/');
}
}
export default withWithWineSessionApiRoute(loginCallback);
/api/withwine/logout
The WithWine logout API route redirects the user to the WithWine authorization
page, the destination is generated by the getLogoutPath
function. Once logged
out the user is returned to the /LogoutCallback
route.
- TypeScript
- JavaScript
import { NextApiRequest, NextApiResponse } from 'next';
import { getLogoutPath } from '@withwine/sdk';
/**
* The WithWine logout route redirects the user to the WithWine authorization
* page, the destination is generated by the `getLogoutPath` function.
* Once logged out the user is returned to the `/LogoutCallback` route.
*/
export default async function Logout(
req: NextApiRequest,
res: NextApiResponse,
) {
const referer = req?.headers.referer;
const destination = getLogoutPath({
req,
siteReturnUrl: referer,
});
res.redirect(307, destination);
}
import { getLogoutPath } from '@withwine/sdk';
/**
* The WithWine logout route redirects the user to the WithWine authorization
* page, the destination is generated by the `getLogoutPath` function.
* Once logged out the user is returned to the `/LogoutCallback` route.
*/
export default async function Logout(req, res) {
const referer = req?.headers.referer;
const destination = getLogoutPath({
req,
siteReturnUrl: referer,
});
res.redirect(307, destination);
}
/api/withwine/logout-callback
The Logout Callback API Route handles the GET response from the WithWine
Authorization API then redirect the user to the original referring page. The
logout
function handles the destruction of the the appropriate cookies.
- TypeScript
- JavaScript
import { NextApiRequest, NextApiResponse } from 'next';
import { logout } from '@withwine/sdk';
import { withWithWineSessionApiRoute } from '@withwine/sdk/next';
/**
* The Logout Callback API Route handles the GET response from the WithWine
* Authorization API then redirect the user to the original referring page.
* The logout function handles the destruction of the the appropriate cookies.
*/
async function logoutCallback(req: NextApiRequest, res: NextApiResponse) {
if (req?.query?.state) {
const state = req.query.state as string;
const result = await logout({
state,
session: req.session,
});
if (result) {
res.redirect(307, result.siteRedirectUrl);
}
} else {
req.session.destroy();
res.redirect(307, '/');
}
}
export default withWithWineSessionApiRoute(
logoutCallback
);
import { logout } from '@withwine/sdk';
import { withWithWineSessionApiRoute } from '@withwine/sdk/next';
/**
* The Logout Callback API Route handles the GET response from the WithWine
* Authorization API then redirect the user to the original referring page.
* The logout function handles the destruction of the the appropriate cookies.
*/
async function logoutCallback(req, res) {
if (req?.query?.state) {
const state = req.query.state;
const result = await logout({
state,
session: req.session,
});
if (result) {
res.redirect(307, result.siteRedirectUrl);
}
} else {
req.session.destroy();
res.redirect(307, '/');
}
}
export default withWithWineSessionApiRoute(logoutCallback);
/api/withwine/token-refresh
The token-refresh API route refreshes the access token for the user if it has
expired. This route is called internally by the
useUser
hook,
you should never need to call it directly.
- TypeScript
- JavaScript
import { NextApiRequest, NextApiResponse } from 'next';
import type { RefreshTokenPayload } from '@withwine/sdk/types';
import { refreshToken } from '@withwine/sdk';
import { withWithWineSessionApiRoute } from '@withwine/sdk/next';
/**
* The token-refresh API route refreshes the access token for the user.
*/
async function tokenRefresh(
req: NextApiRequest,
res: NextApiResponse<RefreshTokenPayload>,
) {
const payload = await refreshToken(req);
if (payload?.success) {
res.status(200).json(payload);
} else {
res.status(200).json({ success: false });
}
}
export default withWithWineSessionApiRoute(tokenRefresh);
import { refreshToken } from '@withwine/sdk';
import { withWithWineSessionApiRoute } from '@withwine/sdk/next';
/**
* The token-refresh API route refreshes the access token for the user.
*/
async function tokenRefresh(req, res) {
const payload = await refreshToken(req);
if (payload?.success) {
res.status(200).json(payload);
} else {
res.status(200).json({ success: false });
}
}
export default withWithWineSessionApiRoute(tokenRefresh);
/api/withwine/user
The user API route checks for the user session and returns it if found,
otherwise it returns the default user object. This API is called by the
useUser
hook
to allow your user data to be used in the UI.
- TypeScript
- JavaScript
import { NextApiRequest, NextApiResponse } from 'next';
import type { User } from '@withwine/sdk/types';
import { defaultUser } from '@withwine/sdk';
import { withWithWineSessionApiRoute } from '@withwine/sdk/next';
/**
* The user API route checks for the user session and returns it if found,
* otherwise it returns the default user object.
*/
async function user(req: NextApiRequest, res: NextApiResponse<User>) {
if (req.session.user) {
res.json(req.session.user);
} else {
res.json(defaultUser);
}
}
export default withWithWineSessionApiRoute(user);
import { defaultUser } from '@withwine/sdk';
import { withWithWineSessionApiRoute } from '@withwine/sdk/next';
/**
* The user API route checks for the user session and returns it if found,
* otherwise it returns the default user object.
*/
async function user(req, res) {
if (req.session.user) {
res.json(req.session.user);
} else {
res.json(defaultUser);
}
}
export default withWithWineSessionApiRoute(user);
Redirect Configuration
If you decide to use top level redirects instead of the direct API calls via
fetch
we first need to add some Next.js specific configuration. in the
withwine.config.json
file we added earlier, we need to add a new block that
will be used to instruct Next.js where to
redirect various requests
during the Authorization Flow. Lets add the nextjs.redirects
config object to
our existing configuration.
At this point you should have all of the necessary API routes in place so you will notice that the redirect configuration we're about to add corresponds with the API routes above.
{
...
"nextjs": {
"redirects": [
{
"source": "/user/logout",
"destination": "/api/withwine/logout",
"permanent": false
},
{
"source": "/user/login",
"destination": "/api/withwine/login",
"permanent": false
},
{
"source": "/user/login/:params*",
"has": [
{
"type": "query",
"key": "loginInfo"
}
],
"destination": "/api/withwine/login:params*",
"permanent": false
},
],
"rewrites": [
{
"source": "/LoginCallback",
"destination": "/api/withwine/login-callback",
"permanent": false
},
{
"source": "/LogoutCallback",
"destination": "/api/withwine/logout-callback",
"permanent": false
}
]
}
}
Callback Rewrites
In the above configuration you'll notice that we have set a /LoginCallback
and
a /LogoutCallback
redirect for the matching API routes, these are important
and are added in this case becasue these values must match the values that have
been set it your website settings in the WithWine backend.
We recommend that you don't change these values unless absolutely necessary, so
even if you are not using redirect based routing for the /login
, /logout
API
routes, it's highly recommended to add the redirects for the callback routes
/login-callback
and /logout-callback
.
{
...
"nextjs": {
"rewrites": [
{
"source": "/LoginCallback",
"destination": "/api/withwine/login-callback",
"permanent": false
},
{
"source": "/LogoutCallback",
"destination": "/api/withwine/logout-callback",
"permanent": false
}
]
}
}
Add Redirects to next.config.js
With our new additions to withwine.config.json
in the root directory we now
need to update next.config.js
so that our redirects above can be implemented.
const withwineConfig = require('./withwine.config.json');
module.exports = {
...
redirects: async () => {
return withwineConfig.nextjs.redirects.length > 0
? withwineConfig.nextjs.redirects
: [];
},
...
};
In the past we would probably have simply added our WithWine specific
configuration to the Next.js serverRuntimeConfig
or publicRuntimeConfig
but as of this writing
these methods have been deprecated,
so we won't be using them in this instance.