@imcorfitz/payload-plugin-oauth-apps v0.3.2
OAuth Apps plugin for Payload CMS
Please note this plugin is under active development. It should
NOTbe considered production ready. A lotWILLchange. Please see TODO
Features
- Ability to create multiple
OAuth Appswith individual client credentials - Better session flow using revokable longer-lived
refresh tokens Session managementon User collections with ability to revoke active sessionsPasswordless authenticationusing One-time password (OTP) or Magiclink- Automatically adds registered OAuth apps to
CSRFandCORSconfig in Payload
Installation
npm install @imcorfitz/payload-plugin-oauth-apps
# or
yarn add @imcorfitz/payload-plugin-oauth-appsRequirements
- Payload ^2.0.0
serverURLis required in yourpayload.config.tsfile
Usage
Setup plugin
// payload.config.ts
import { oAuthApps } from "@imcorfitz/payload-plugin-oauth-apps";
export default buildConfig({
// ... Payload Config
plugins: [
// ... other plugins
oAuthApps({
userCollections: [Users.slug],
}),
],
});Options
userCollections: string[] | requiredAn array of collections slugs to enable OAuth Sessions. Enabled collections receive an
OAuthgroup with a sessions array, listing all currently active sessions.access: object | optionalAllows you to configure field-level access control on the fields in the
OAuthgroup on configured user collections.sessions: object | optionalread: FieldAccess | optionalcreate: FieldAccess | optionalupdate: FieldAccess | optional
authorization: object | optionalConfigure how
OAuth Appsauthorize users and initialize new sessions.customHandlers: {<custom_method>: EndpointHandler} | optionalotpExpiration: number | optionalgenerateOTP: method | optionalgenerateEmailVariables: method | optional
When using
otpand authorization method, you can set the expiration (otpExpiration- defaults to 10 minutes) and customise how you want the one-time password to be generated (generateOTP- defaults to generating a 6-digit number).Both
magiclinkandotpallows you to set thegenerateEmailVariablesmethod to customise the email variables available in the OAuth App settings. In both method you will have access to following properties:req: PayloadRequestvariables: An object containing a magiclink and token, or an OTP, depending on themethoduser: Information about the user to be authenticatedclient: Details about the OAuth App making the auth request
Note:
customHandlersshould be set if you wish to create your ownmethodand allows you to perform the entire authentication flow yourself. Note that the plugin does expose the generateAccessToken and generateRefreshToken methods, however this goes beyond the scope of this documentation, and should be used in advanced cases only.sessions: object | optionalConfiguration of the sessions created.
limit: number | optionalipinfoApiKey: string | optionalfetchLocationInfo: method | optionalrefreshTokenExpiration: number | optional
Allows you set a
limitof number of sessions per user. If not set, users are free to create unlimited sessions (not adviced). When set, oldest session will be removed when limit has been reached and a new session is initialised.By default all refresh tokens have a lifespan of 30 days. You can override this by passing
refreshTokenExpirationwith the amount of seconds a refresh token should be valid for.The plugin uses
IPInfoto fetch location information whenever a session is created. To use this, simply set your ownipinfoApiKey. (Please note, that it doesn't work on localhost). If you wish to use an alternative location detection service, feel free to use thefetchLocationInfomethod which gives you following properties:req: PayloadRequestip: The detected IP address | possibly undefined
Add OAuth Manager
Add the oAuthManager field to your admin user collection. This determines which users have access to manage OAuth Apps in Payload CMS.
// collections/admins.ts
import { oAuthManager } from "@imcorfitz/payload-plugin-oauth-apps";
const Admins: CollectionConfig = {
slug: "admins",
auth: true,
// ... Collection config
fields: [
// ... Other fields
oAuthManager({
// NOTE: You can pass Checkbox field properties here to override all field properties except for: name, label and type.
access: {
update: isAdminFieldLevel,
},
admin: {
readOnly: false,
},
}),
],
};OAuth REST API endpoints
POST
oauth/authorize:Used by OAuth apps to log in users. Upon sucessful login, the response will contain an access token and a refresh token. By passing
methodas part of the body, you can tell Payload CMS how you wish to authenticate the user. The plugin supportcredentials,otp, andmagiclinkout of the box.Note: Don't ever expose your client id or client secret to the client. These operations should always be made securely from server-side.
Parameter Description email requiredThe email address of the user to be logged in clientId requiredThe client id of the OAuth App performing the operation clientSecret requiredThe client secret of the OAuth App performing the operation method 'credentials' \| 'otp' \| 'magiclink' \| <custom_method>.The defaultmethodis 'credentials'password The password of the user to be logged in. NB: requiredifauthorization.methodis set to 'credentials'// Request const response = await fetch(`https://my.payloadcms.tld/<user-collection>/oauth/authorize`, { method: 'POST', body: JSON.stringify({ method: "credentials", email: "user@payloadcms.com", password: "very-safe-password-1234", clientId: "CID_s3o8y384y5...", clientSecret: "CS_skijorintg..." }) }) // Successful Response { "accessToken": "eyJhbGciOiJIUzI1N...XMnxpb1NTK9K0", "accessExpiration": 3600, "refreshToken": "43d5cc1ee66ac880...94b8f2df", "refreshExpiration": 2592000 }POST
oauth/refresh-token:Used by OAuth apps to request a new access token using their issued refresh token. Upon sucessful login, the response will contain an access token and a refresh token.
Note: Don't ever expose your client id or client secret to the client. These operations should always be made securely from server-side.
Parameter Description refreshToken requiredThe refresh token issued at authorization clientId requiredThe client id of the OAuth App performing the operation clientSecret requiredThe client secret of the OAuth App performing the operation // Request const response = await fetch(`https://my.payloadcms.tld/<user-collection>/oauth/refresh-token`, { method: 'POST', body: JSON.stringify({ refreshToken: "43d5cc1ee66ac880...94b8f2df", clientId: "CID_s3o8y384y5...", clientSecret: "CS_skijorintg..." }) }) // Successful Response { "accessToken": "eyJhbGciOiJIUAhd7...XMnxpVbUoAyhI", "accessExpiration": 3600, }POST
oauth/verify-otp:When
methodis set to 'otp', the user will receive an email with a one-time password. Use this endpoint to finalize the authentication process and receive an access and refresh token.Note: Don't ever expose your client id or client secret to the client. These operations should always be made securely from server-side.
Parameter Description email requiredThe email address of the user to be logged in otp requiredThe one-time password received by the user by email clientId requiredThe client id of the OAuth App performing the operation clientSecret requiredThe client secret of the OAuth App performing the operation // Request const response = await fetch(`https://my.payloadcms.tld/<user-collection>/oauth/verify-otp`, { method: 'POST', body: JSON.stringify({ email: "user@payloadcms.com", otp: "123456", clientId: "CID_s3o8y384y5...", clientSecret: "CS_skijorintg..." }) }) // Successful Response { "accessToken": "eyJhbGciOiJIUzI1N...XMnxpb1NTK9K0", "accessExpiration": 3600, "refreshToken": "43d5cc1ee66ac880...94b8f2df", "refreshExpiration": 2592000 }GET
oauth/verify-magiclink:When
methodis set to 'magiclink', the user will receive an email with a link. The link is directing the user to this endpoint by default (if not overridden in OAuth App settings). When validated, the user will be redirected to the callbackUrl registered for the OAuth App.Query Parameter Description token requiredThe token received by the user by email POST
oauth/verify-magiclink:Same endpoint can also be used by an OAuth app to post the user's token for validation. Same process applies, but instead of a redirect, the call will output a JSON object with the status of the validation.
Parameter Description token requiredThe token received by the user by email POST
oauth/verify-code:When
methodis set to 'magiclink' and the user has clicked the link they've received calling this endpoint with the code received at the authentication call, this endpoint will verify your code and finalize the authentication process and issue an access and refresh token.Note: Don't ever expose your client id or client secret to the client. These operations should always be made securely from server-side.
Parameter Description email requiredThe email address of the user to be logged in code requiredThe code received during authentication call clientId requiredThe client id of the OAuth App performing the operation clientSecret requiredThe client secret of the OAuth App performing the operation // Request const response = await fetch(`https://my.payloadcms.tld/<user-collection>/oauth/verify-code`, { method: 'POST', body: JSON.stringify({ email: "user@payloadcms.com", code: "AbCdEf123456", clientId: "CID_s3o8y384y5...", clientSecret: "CS_skijorintg..." }) }) // Successful Response { "accessToken": "eyJhbGciOiJIUzI1N...XMnxpb1NTK9K0", "accessExpiration": 3600, "refreshToken": "43d5cc1ee66ac880...94b8f2df", "refreshExpiration": 2592000 }
Changelog
Please see CHANGELOG for more information what has changed recently.
Known issues
Reset password
Currently Payload doesn't feature operation hooks on reset password, and it automatically initialises a session and issues an access token when the operation is done. This is not a problem when operating within the CMS; however, it doesn't allow for this plugin to limit the session creation to be CMS-only – meaning that an OAuth application is all good to use the reset password REST endpoint and GraphQL mutation native to Payload, but this will only create an access token, that will be shortlived and not accompanied by a refresh token. It is therefor adviced for OAuth applications to disregard the session and access token issued by Payload post reset password and request the user to log in again after the password has been reset.
Disclaimer
Payload 2.0
This plugin was initially written to work with Payload ^1.0.0. An effort has been made to match ^2.0.0, thus leaving behind the legacy ^1.0.0 versions. It should be working fine however, I have yet to test the plugin using the vite-bundler and postgres db adapter.
GraphQL
The entire auth, refresh and logout flow is fully working using the REST api. I have yet to create dedicated GraphQL mutations and resolvers. This is in the works.
Contributing
Contributions and feedback are very welcome.
To get it running:
- Clone the project.
yarn installyarn build
Credits
License
The MIT License (MIT). Please see License File for more information.