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

Add setSession method #198

Closed
wants to merge 2 commits into from
Closed
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
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,53 @@ If the user is authenticated then your API route will simply execute, but if the
}
```

### Setting the session with a token set

In case you want to set session tokens programmatically you can use the `setSession` method.

This is useful for example if you need interoperability between `@auth0/nextjs-auth0` and an existing legacy sign up endpoint which uses the password grant type for silent authentication.

If you want to expose a route which creates a new user and then silently authenticates them (eg: `/pages/api/signup.js`):

```js
// Importing the `auth0` package to use the password grant type
import { AuthenticationClient } from 'auth0';
import auth0 from '../../utils/auth0';
import { createUserInUpstreamService } from '../../utils/signup';

const auth0AuthClient = new AuthenticationClient({
domain: process.env.NEXT_PUBLIC_AUTH0_DOMAIN,
clientId: process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET
});

export default async function signup(req, res) {
try {
// POST http://api.acme.com/signup will create
// the user in the ACME database and in Auth0
await createUserInUpstreamService(req.body);

// Once the user is created successfully then silently authenticate
// them using the password grant type
const tokens = await auth0AuthClient.oauth.passwordGrant({
username: req.body.username,
password: req.body.password,
scope: 'openid profile email offline_access'
});

// Set the session with the tokens from the password grant
await auth0.setSession(req, res, tokens);

res.status(201).end();
} catch (error) {
console.error(error);
res.status(error.status || 500).end(error.message);
}
}
```

NOTE: For the above example to work you will need to enable the password grant type on your Auth0 client application settings dashboard.

## Documentation

### Cookies
Expand Down
2 changes: 2 additions & 0 deletions src/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import LogoutHandler from './logout';
import CallbackHandler from './callback';
import ProfileHandler from './profile';
import SessionHandler from './session';
import SetSessionHandler from './set-session';
import RequireAuthentication from './require-authentication';
import TokenCache from './token-cache';

Expand All @@ -12,6 +13,7 @@ export default {
LogoutHandler,
ProfileHandler,
SessionHandler,
SetSessionHandler,
RequireAuthentication,
TokenCache
};
22 changes: 22 additions & 0 deletions src/handlers/set-session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { TokenSetParameters, TokenSet } from 'openid-client';
import { ISessionStore } from '../session/store';
import getSessionFromTokenSet from '../utils/session';

export default function setSessionHandler(sessionStore: ISessionStore) {
return async (req: NextApiRequest, res: NextApiResponse, tokenSetParameters: TokenSetParameters): Promise<void> => {
if (!res) {
throw new Error('Response is not available');
}

if (!req) {
throw new Error('Request is not available');
}

// Get the claims without any OIDC specific claim.
const session = getSessionFromTokenSet(new TokenSet(tokenSetParameters));

// Create the session.
await sessionStore.save(req, res, session);
};
}
3 changes: 3 additions & 0 deletions src/instance.browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ export default function createDummyBrowserInstance(): ISignInWithAuth0 & { isBro
getSession: (): Promise<ISession | null | undefined> => {
throw new Error('The getSession method can only be used from the server side');
},
setSession: (): Promise<void> => {
throw new Error('The setSession method can only be used from the server side');
},
requireAuthentication: () => (): Promise<void> => {
throw new Error('The requireAuthentication method can only be used from the server side');
},
Expand Down
1 change: 1 addition & 0 deletions src/instance.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export default function createInstance(settings: IAuth0Settings): ISignInWithAut
handleCallback: handlers.CallbackHandler(settings, clientProvider, store),
handleProfile: handlers.ProfileHandler(store, clientProvider),
getSession: handlers.SessionHandler(store),
setSession: handlers.SetSessionHandler(store),
requireAuthentication: handlers.RequireAuthentication(store),
tokenCache: handlers.TokenCache(clientProvider, store)
};
Expand Down
6 changes: 6 additions & 0 deletions src/instance.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { IncomingMessage } from 'http';
import { TokenSetParameters } from 'openid-client';
import { ISession } from './session/session';
import { LoginOptions } from './handlers/login';
import { ITokenCache } from './tokens/token-cache';
Expand Down Expand Up @@ -34,6 +35,11 @@ export interface ISignInWithAuth0 {
*/
getSession: (req: IncomingMessage) => Promise<ISession | null | undefined>;

/**
* Set session handler which sets the current session with a token set.
*/
setSession: (req: NextApiRequest, res: NextApiResponse, tokenSetParameters: TokenSetParameters) => Promise<void>;

/**
* Handle to require authentication for an API route.
*/
Expand Down
79 changes: 79 additions & 0 deletions tests/handlers/set-session.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import base64url from 'base64url';
import handlers from '../../src/handlers';
import { ISessionStore } from '../../src/session/store';
import getRequestResponse from '../helpers/http';

describe('set session handler', () => {
let store: ISessionStore;

const tokenSetParams = {
access_token: 'my-access-token',
refresh_token: 'my-refresh-token',
id_token: `jwt-header.${base64url.encode(
JSON.stringify({
email: 'foo@bar.com',
email_verified: false,
name: 'Foo Bar',
nickname: 'foobar',
picture: 'http://example.com/image',
sub: 'user-id',
updated_at: '2020-12-01T12:15:06.383Z'
})
)}.my-verify-signature`,
scope: 'openid profile email offline_access',
expires_in: 2592000,
token_type: 'Bearer'
};

beforeEach(() => {
store = {
read: jest.fn().mockResolvedValue({}),
save: jest.fn().mockResolvedValue({})
};
});

test('should require a truthy request object', async () => {
const { res } = getRequestResponse();
const sessionHandler = handlers.SetSessionHandler(store);

await expect(sessionHandler(null as any, res, tokenSetParams)).rejects.toEqual(
new Error('Request is not available')
);
});

test('should require a truthy response object', async () => {
const { req } = getRequestResponse();
const sessionHandler = handlers.SetSessionHandler(store);

await expect(sessionHandler(req, null as any, tokenSetParams)).rejects.toEqual(
new Error('Response is not available')
);
});

test('should set the session', async () => {
const { req, res } = getRequestResponse();

const setSessionHandler = handlers.SetSessionHandler(store);

await setSessionHandler(req, res, tokenSetParams);

expect(store.save).toHaveBeenCalledWith(req, res, {
accessToken: 'my-access-token',
accessTokenExpiresAt: expect.any(Number),
accessTokenScope: 'openid profile email offline_access',
createdAt: expect.any(Number),
idToken:
'jwt-header.eyJlbWFpbCI6ImZvb0BiYXIuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiRm9vIEJhciIsIm5pY2tuYW1lIjoiZm9vYmFyIiwicGljdHVyZSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9pbWFnZSIsInN1YiI6InVzZXItaWQiLCJ1cGRhdGVkX2F0IjoiMjAyMC0xMi0wMVQxMjoxNTowNi4zODNaIn0.my-verify-signature',
refreshToken: 'my-refresh-token',
user: {
email: 'foo@bar.com',
email_verified: false,
name: 'Foo Bar',
nickname: 'foobar',
picture: 'http://example.com/image',
sub: 'user-id',
updated_at: '2020-12-01T12:15:06.383Z'
}
});
});
});