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

Compatibility with Medusa 1.17 sessions #99

Merged
merged 9 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 16 additions & 1 deletion docs/pages/authentication/auth0.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ newly added plugins. To do so here are the steps

<CircleStep index={4}>
Update your client to add the authentication action

After authentication is successfull, the user will be redirected to the `successRedirect` url with a `?access_token=your_token` query param and a session cookie will be set. Session will automatically be authenticated. The access token is a JWT that can be used with Authorization: Bearer <token> header to authenticate requests to the backend instead of using a session cookie.
</CircleStep>

### Default behaviour
Expand All @@ -100,4 +102,17 @@ The default `verifyCallback` flow looks as follow (unless the `strict` option is
- In the case another external authentication method have been used in the past, then an unauthorized
will be returned.
- if the customer trying to authenticate does not exist, a new customer will be created and the authentication
flow follow the previous point
flow follow the previous point

### Request only access token in a JSON response

If you are using your Medusa application with a SPA or mobile frontend, you can request opt to receive the access token in a JSON response instead of a redirect.

To do this, you have to add ?returnAccessToken=true query parameter to your authentication call. Your authentication URL will then look like this: ${medusa_url}/${authPath}?returnAccessToken=true

The response will look like this:
```json
{
access_token: "<your-jwt-token>"
}
```
17 changes: 16 additions & 1 deletion docs/pages/authentication/azureoidc.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ newly added plugins. To do so here are the steps
```
- `medusa_url` correspond to your backend server url (e.g `http://localhost:9000`)
- `authPath` correspond to `authPath` configuration in your plugin (e.g `admin/auth/azure`)

After authentication is successfull, the user will be redirected to the `successRedirect` url with a `?access_token=your_token` query param and a session cookie will be set. Session will automatically be authenticated. The access token is a JWT that can be used with Authorization: Bearer <token> header to authenticate requests to the backend instead of using a session cookie.
</CircleStep>

### Implementation Comments
Expand All @@ -131,4 +133,17 @@ The default `verifyCallback` flow looks as follow (unless the `strict` option is
- In the case another external authentication method have been used in the past, then an unauthorized
will be returned.
- if the customer trying to authenticate does not exist, a new customer will be created and the authentication
flow follow the previous point
flow follow the previous point

### Request only access token in a JSON response

If you are using your Medusa application with a SPA or mobile frontend, you can request opt to receive the access token in a JSON response instead of a redirect.

To do this, you have to add ?returnAccessToken=true query parameter to your authentication call. Your authentication URL will then look like this: ${medusa_url}/${authPath}?returnAccessToken=true

The response will look like this:
```json
{
access_token: "<your-jwt-token>"
}
```
17 changes: 16 additions & 1 deletion docs/pages/authentication/facebook.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ newly added plugins. To do so here are the steps
```
- `medusa_url` correspond to your backend server url (e.g `http://localhost:9000`)
- `authPath` correspond to `authPath` configuration in your plugin (e.g `admin/auth/facebook`)

After authentication is successfull, the user will be redirected to the `successRedirect` url with a `?access_token=your_token` query param and a session cookie will be set. Session will automatically be authenticated. The access token is a JWT that can be used with Authorization: Bearer <token> header to authenticate requests to the backend instead of using a session cookie.
</CircleStep>

### Default behaviour
Expand All @@ -106,4 +108,17 @@ The default `verifyCallback` flow looks as follow (unless the `strict` option is
- In the case another external authentication method have been used in the past, then an unauthorized
will be returned.
- if the customer trying to authenticate does not exist, a new customer will be created and the authentication
flow follow the previous point
flow follow the previous point

### Request only access token in a JSON response

If you are using your Medusa application with a SPA or mobile frontend, you can request opt to receive the access token in a JSON response instead of a redirect.

To do this, you have to add ?returnAccessToken=true query parameter to your authentication call. Your authentication URL will then look like this: ${medusa_url}/${authPath}?returnAccessToken=true

The response will look like this:
```json
{
access_token: "<your-jwt-token>"
}
```
17 changes: 16 additions & 1 deletion docs/pages/authentication/google.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ newly added plugins. To do so here are the steps
```
- `medusa_url` correspond to your backend server url (e.g `http://localhost:9000`)
- `authPath` correspond to `authPath` configuration in your plugin (e.g `admin/auth/google`)

After authentication is successfull, the user will be redirected to the `successRedirect` url with a `?access_token=your_token` query param and a session cookie will be set. Session will automatically be authenticated. The access token is a JWT that can be used with Authorization: Bearer <token> header to authenticate requests to the backend instead of using a session cookie.
</CircleStep>

### Default behaviour
Expand All @@ -106,4 +108,17 @@ The default `verifyCallback` flow looks as follow (unless the `strict` option is
- In the case another external authentication method have been used in the past, then an unauthorized
will be returned.
- if the customer trying to authenticate does not exist, a new customer will be created and the authentication
flow follow the previous point
flow follow the previous point

### Request only access token in a JSON response

If you are using your Medusa application with a SPA or mobile frontend, you can request opt to receive the access token in a JSON response instead of a redirect.

To do this, you have to add ?returnAccessToken=true query parameter to your authentication call. Your authentication URL will then look like this: ${medusa_url}/${authPath}?returnAccessToken=true

The response will look like this:
```json
{
access_token: "<your-jwt-token>"
}
```
17 changes: 16 additions & 1 deletion docs/pages/authentication/linkedin.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ newly added plugins. To do so here are the steps
```
- `medusa_url` correspond to your backend server url (e.g `http://localhost:9000`)
- `authPath` correspond to `authPath` configuration in your plugin (e.g `admin/auth/linkedin`)

After authentication is successfull, the user will be redirected to the `successRedirect` url with a `?access_token=your_token` query param and a session cookie will be set. Session will automatically be authenticated. The access token is a JWT that can be used with Authorization: Bearer <token> header to authenticate requests to the backend instead of using a session cookie.
</CircleStep>

### Default behaviour
Expand All @@ -106,4 +108,17 @@ The default `verifyCallback` flow looks as follow (unless the `strict` option is
- In the case another external authentication method have been used in the past, then an unauthorized
will be returned.
- if the customer trying to authenticate does not exist, a new customer will be created and the authentication
flow follow the previous point
flow follow the previous point

### Request only access token in a JSON response

If you are using your Medusa application with a SPA or mobile frontend, you can request opt to receive the access token in a JSON response instead of a redirect.

To do this, you have to add ?returnAccessToken=true query parameter to your authentication call. Your authentication URL will then look like this: ${medusa_url}/${authPath}?returnAccessToken=true

The response will look like this:
```json
{
access_token: "<your-jwt-token>"
}
```
4 changes: 2 additions & 2 deletions packages/medusa-plugin-auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@
"test:ci": "yarn add -D @medusajs/medusa@${MEDUSAJS_VERSION} && yarn run test"
},
"peerDependencies": {
"@medusajs/medusa": "<=1.16.x",
"@medusajs/medusa": ">=1.16.x",
"passport": "^0.6.0",
"typeorm": "*"
},
"devDependencies": {
"@medusajs/medusa": "<=1.16.x",
"@medusajs/medusa": ">=1.17.x",
"@types/express": "^4.17.17",
"@types/jest": "^29.1.2",
"jest": "^29.1.2",
Expand Down
29 changes: 15 additions & 14 deletions packages/medusa-plugin-auth/src/auth-strategies/firebase/utils.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import passport from 'passport';
import cors from 'cors';
import { ConfigModule } from '@medusajs/medusa/dist/types/global';
import { Router } from 'express';
import { TWENTY_FOUR_HOURS_IN_MS } from '../../types';
import { sendTokenFactory } from '../../core/auth-callback-middleware';

function firebaseCallbackMiddleware(domain: 'admin' | 'store', secret: string, expiresIn: number) {
return (req, res) => {
const sendToken = sendTokenFactory(domain, secret, expiresIn);
sendToken(req, res);
res.status(200).json({ result: 'OK' });
import { Router, Request, Response } from 'express';
import { authenticateSessionFactory, signToken } from '../../core/auth-callback-middleware';

function firebaseCallbackMiddleware(domain: 'admin' | 'store', configModule: ConfigModule, expiresIn?: number) {
return (req: Request, res: Response) => {
if(req.query.returnAccessToken == 'true') {
const token = signToken(domain, configModule, req.user, expiresIn);
res.json({ access_token: token });
return;
} else {
authenticateSessionFactory(domain)(req, res);

res.status(200).json({ result: 'OK' });
}
};
}

Expand Down Expand Up @@ -40,11 +45,7 @@ export function firebaseAuthRoutesBuilder({
/*necessary if you are using non medusajs client such as a pure axios call, axios initially requests options and then get*/
router.options(authPath, cors(corsOptions));

const callbackHandler = firebaseCallbackMiddleware(
domain,
configModule.projectConfig.jwt_secret,
expiresIn ?? TWENTY_FOUR_HOURS_IN_MS
);
const callbackHandler = firebaseCallbackMiddleware(domain, configModule, expiresIn);

router.get(
authPath,
Expand Down
46 changes: 29 additions & 17 deletions packages/medusa-plugin-auth/src/core/auth-callback-middleware.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,44 @@
import { Request, Response } from 'express';
import { ConfigModule } from '@medusajs/medusa/dist/types/global';
import jwt from 'jsonwebtoken';

/**
* Return the handler of the auth callback for an auth strategy. Once the auth is successful this callback
* will be called.
* @param domain
* @param secret
* @param expiresIn
* @param successRedirectGetter
* @param successAction
*/
export function authCallbackMiddleware(
domain: 'admin' | 'store',
secret: string,
expiresIn: number,
successRedirectGetter: () => string
successAction: (req: Request, res: Response) => void
) {
return (req, res) => {
const sendToken = sendTokenFactory(domain, secret, expiresIn);
sendToken(req, res);
res.redirect(successRedirectGetter());
successAction(req, res);
};
}

export function sendTokenFactory(domain: 'admin' | 'store', secret: string, expiresIn: number) {
export function signToken(domain: 'admin' | 'store', configModule: ConfigModule, user: any, expiresIn?: number) {
if(domain === 'admin') {
return jwt.sign(
{ user_id: user.id, domain: 'admin' },
configModule.projectConfig.jwt_secret,
{
expiresIn: expiresIn ?? '24h',
}
);
} else {
return jwt.sign(
{ customer_id: user.id, domain: 'store' },
configModule.projectConfig.jwt_secret,
{
expiresIn: expiresIn ?? '30d',
}
);
}
}

export function authenticateSessionFactory(domain: 'admin' | 'store') {
return (req, res) => {
const tokenData =
domain === 'admin' ? { userId: req.user.id, ...req.user } : { customer_id: req.user.id, ...req.user };
const token = jwt.sign(tokenData, secret, { expiresIn });
const sessionKey = domain === 'admin' ? 'jwt' : 'jwt_store';
req.session[sessionKey] = token;
const sessionKey = domain === 'admin' ? 'user_id' : 'customer_id';

req.session[sessionKey] = req.user.id;
};
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { Router } from 'express';
import { Request, Response, Router } from 'express';
import passport from 'passport';
import cors from 'cors';
import { TWENTY_FOUR_HOURS_IN_MS } from '../../../types';
import { authCallbackMiddleware } from '../../auth-callback-middleware';
import { authCallbackMiddleware, authenticateSessionFactory, signToken } from '../../auth-callback-middleware';
import { ConfigModule } from '@medusajs/medusa/dist/types/global';

type PassportAuthenticateMiddlewareOptions = {
[key: string]: unknown;
scope?: unknown;
scope?: string | string[];
};

type PassportCallbackAuthenticateMiddlewareOptions = {
Expand All @@ -34,23 +33,24 @@ export function passportAuthRoutesBuilder({
strategyName,
passportAuthenticateMiddlewareOptions,
passportCallbackAuthenticateMiddlewareOptions,
expiresIn,
successRedirect,
authCallbackPath,
expiresIn,
}: {
domain: 'admin' | 'store';
configModule: ConfigModule;
authPath: string;
strategyName: string;
passportAuthenticateMiddlewareOptions: PassportAuthenticateMiddlewareOptions;
passportCallbackAuthenticateMiddlewareOptions: PassportCallbackAuthenticateMiddlewareOptions;
expiresIn?: number;
successRedirect: string;
authCallbackPath: string;
expiresIn?: number;
}): Router {
const router = Router();

const originalSuccessRedirect = successRedirect;
const defaultRedirect = successRedirect;
let successAction: (req: Request, res: Response) => void;

const corsOptions = {
origin:
Expand All @@ -67,7 +67,7 @@ export function passportAuthRoutesBuilder({
authPath,
(req, res, next) => {
// Allow to override the successRedirect by passing a query param `?redirectTo=your_url`
successRedirect = (req.query.redirectTo ? req.query.redirectTo : originalSuccessRedirect) as string;
successAction = successActionHandlerFactory(req, domain, configModule, defaultRedirect, expiresIn);
next();
},
passport.authenticate(strategyName, {
Expand All @@ -76,12 +76,7 @@ export function passportAuthRoutesBuilder({
})
);

const callbackHandler = authCallbackMiddleware(
domain,
configModule.projectConfig.jwt_secret,
expiresIn ?? TWENTY_FOUR_HOURS_IN_MS,
() => successRedirect
);
const callbackHandler = authCallbackMiddleware((req, res) => successAction(req, res));

router.get(authCallbackPath, cors(corsOptions));
router.get(
Expand Down Expand Up @@ -119,3 +114,29 @@ export function passportAuthRoutesBuilder({

return router;
}

function successActionHandlerFactory(req: Request, domain: 'admin' | 'store', configModule: ConfigModule, defaultRedirect: string, expiresIn?: number) {
const returnAccessToken = req.query.returnAccessToken == 'true';
const redirectUrl = (req.query.redirectTo ? req.query.redirectTo : defaultRedirect) as string;

if (returnAccessToken) {
return (req: Request, res: Response) => {
const token = signToken(domain, configModule, req.user, expiresIn);
res.json({ access_token: token });
};
}

return (req: Request, res: Response) => {
const authenticateSession = authenticateSessionFactory(domain);
authenticateSession(req, res);

const token = signToken(domain, configModule, req.user, expiresIn);


// append token to redirect url as query param
const url = new URL(redirectUrl);
url.searchParams.append('access_token', token);

res.redirect(url.toString());
};
}
Loading
Loading