fix(auth): add state parameters and PKCE support
Only the OAuth2 auth strategy was using the state parameter, which should be used as described in the RFC. The other auth strategies such as GitHub, GitLab or Google were lacking the state parameter. This change adds the required state parameter as well as enabling PKCE support on providers where it's possible. Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
@@ -106,6 +106,19 @@
|
|||||||
"email": "change or delete this: attribute map for `email` (default: NameID)"
|
"email": "change or delete this: attribute map for `email` (default: NameID)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"oauth2": {
|
||||||
|
"baseURL": "https://auth.example.com/",
|
||||||
|
"userProfileURL": "https://auth.example.com/oauth2/userinfo/",
|
||||||
|
"tokenURL": "https://auth.example.com/oauth2/token/",
|
||||||
|
"authorizationURL": "https://auth.example.com/oauth2/authorize/",
|
||||||
|
"clientID": "change-this-id",
|
||||||
|
"clientSecret": "change-this-secret",
|
||||||
|
"scope": "openid profile user",
|
||||||
|
"userProfileUsernameAttr": "preferred_username",
|
||||||
|
"userProfileEmailAttr": "email",
|
||||||
|
"userProfileDisplayNameAttr": "name",
|
||||||
|
"pkce": true
|
||||||
|
},
|
||||||
"imgur": {
|
"imgur": {
|
||||||
"clientID": "change this"
|
"clientID": "change this"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -210,8 +210,8 @@ these are rarely used for various reasons.
|
|||||||
### OAuth2 Login
|
### OAuth2 Login
|
||||||
|
|
||||||
| config file | environment | **default** and example value | description |
|
| config file | environment | **default** and example value | description |
|
||||||
|-------------|---------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|-------------|---------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `oauth2` | | `{baseURL: ..., userProfileURL: ..., userProfileUsernameAttr: ..., userProfileDisplayNameAttr: ..., userProfileEmailAttr: ..., tokenURL: ..., authorizationURL: ..., clientID: ..., clientSecret: ..., scope: ...}` | An object detailing your OAuth2 provider. Refer to the [Mattermost](guides/auth/mattermost-self-hosted.md) or [Nextcloud](guides/auth/nextcloud.md) examples for more details! |
|
| `oauth2` | | `{baseURL: ..., userProfileURL: ..., userProfileUsernameAttr: ..., userProfileDisplayNameAttr: ..., userProfileEmailAttr: ..., tokenURL: ..., authorizationURL: ..., clientID: ..., clientSecret: ..., scope: ..., pkce: ...}` | An object detailing your OAuth2 provider. Refer to the [Mattermost](guides/auth/mattermost-self-hosted.md) or [Nextcloud](guides/auth/nextcloud.md) examples for more details! |
|
||||||
| | `CMD_OAUTH2_USER_PROFILE_URL` | **no default**, `https://example.com` | Where to retrieve information about a user after successful login. Needs to output JSON. (no default value) Refer to the [Mattermost](guides/auth/mattermost-self-hosted.md) or [Nextcloud](guides/auth/nextcloud.md) examples for more details on all of the `CMD_OAUTH2...` options. |
|
| | `CMD_OAUTH2_USER_PROFILE_URL` | **no default**, `https://example.com` | Where to retrieve information about a user after successful login. Needs to output JSON. (no default value) Refer to the [Mattermost](guides/auth/mattermost-self-hosted.md) or [Nextcloud](guides/auth/nextcloud.md) examples for more details on all of the `CMD_OAUTH2...` options. |
|
||||||
| | `CMD_OAUTH2_USER_PROFILE_USERNAME_ATTR` | **no default**, `name` | where to find the username in the JSON from the user profile URL. (no default value) |
|
| | `CMD_OAUTH2_USER_PROFILE_USERNAME_ATTR` | **no default**, `name` | where to find the username in the JSON from the user profile URL. (no default value) |
|
||||||
| | `CMD_OAUTH2_USER_PROFILE_DISPLAY_NAME_ATTR` | **no default**, `display-name` | where to find the display-name in the JSON from the user profile URL. (no default value) |
|
| | `CMD_OAUTH2_USER_PROFILE_DISPLAY_NAME_ATTR` | **no default**, `display-name` | where to find the display-name in the JSON from the user profile URL. (no default value) |
|
||||||
@@ -225,6 +225,7 @@ these are rarely used for various reasons.
|
|||||||
| | `CMD_OAUTH2_SCOPE` | **no default**, `openid email profile` | Scope to request for OIDC (OpenID Connect) providers. |
|
| | `CMD_OAUTH2_SCOPE` | **no default**, `openid email profile` | Scope to request for OIDC (OpenID Connect) providers. |
|
||||||
| | `CMD_OAUTH2_ROLES_CLAIM` | **no default**, `roles` | ID token claim, which is supposed to provide an array of strings of roles |
|
| | `CMD_OAUTH2_ROLES_CLAIM` | **no default**, `roles` | ID token claim, which is supposed to provide an array of strings of roles |
|
||||||
| | `CMD_OAUTH2_ACCESS_ROLE` | **no default**, `role/hedgedoc` | The role which should be included in the ID token roles claim to grant access |
|
| | `CMD_OAUTH2_ACCESS_ROLE` | **no default**, `role/hedgedoc` | The role which should be included in the ID token roles claim to grant access |
|
||||||
|
| | `CMD_OAUTH2_PKCE` | **`false`**, `true` | Whether to use PKCE auth. Defaults to false since not every OAuth2 provider supports it. |
|
||||||
|
|
||||||
!!! info
|
!!! info
|
||||||
If you are using a [CA not trusted by Node.js](https://github.com/nodejs/node/issues/4175) (like Let's Encrypt e.g) for
|
If you are using a [CA not trusted by Node.js](https://github.com/nodejs/node/issues/4175) (like Let's Encrypt e.g) for
|
||||||
|
|||||||
@@ -104,7 +104,8 @@ module.exports = {
|
|||||||
tokenURL: undefined,
|
tokenURL: undefined,
|
||||||
clientID: undefined,
|
clientID: undefined,
|
||||||
clientSecret: undefined,
|
clientSecret: undefined,
|
||||||
scope: undefined
|
scope: undefined,
|
||||||
|
pkce: false
|
||||||
},
|
},
|
||||||
facebook: {
|
facebook: {
|
||||||
clientID: undefined,
|
clientID: undefined,
|
||||||
|
|||||||
@@ -115,7 +115,8 @@ module.exports = {
|
|||||||
clientSecret: process.env.CMD_OAUTH2_CLIENT_SECRET,
|
clientSecret: process.env.CMD_OAUTH2_CLIENT_SECRET,
|
||||||
scope: process.env.CMD_OAUTH2_SCOPE,
|
scope: process.env.CMD_OAUTH2_SCOPE,
|
||||||
rolesClaim: process.env.CMD_OAUTH2_ROLES_CLAIM,
|
rolesClaim: process.env.CMD_OAUTH2_ROLES_CLAIM,
|
||||||
accessRole: process.env.CMD_OAUTH2_ACCESS_ROLE
|
accessRole: process.env.CMD_OAUTH2_ACCESS_ROLE,
|
||||||
|
pkce: toBooleanConfig(process.env.CMD_OAUTH2_PKCE)
|
||||||
},
|
},
|
||||||
dropbox: {
|
dropbox: {
|
||||||
clientID: process.env.CMD_DROPBOX_CLIENTID,
|
clientID: process.env.CMD_DROPBOX_CLIENTID,
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ passport.use(new DropboxStrategy({
|
|||||||
apiVersion: '2',
|
apiVersion: '2',
|
||||||
clientID: config.dropbox.clientID,
|
clientID: config.dropbox.clientID,
|
||||||
clientSecret: config.dropbox.clientSecret,
|
clientSecret: config.dropbox.clientSecret,
|
||||||
callbackURL: config.serverURL + '/auth/dropbox/callback'
|
callbackURL: config.serverURL + '/auth/dropbox/callback',
|
||||||
|
state: true,
|
||||||
|
pkce: true
|
||||||
}, passportGeneralCallback))
|
}, passportGeneralCallback))
|
||||||
|
|
||||||
dropboxAuth.get('/auth/dropbox', function (req, res, next) {
|
dropboxAuth.get('/auth/dropbox', function (req, res, next) {
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ const facebookAuth = module.exports = Router()
|
|||||||
passport.use(new FacebookStrategy({
|
passport.use(new FacebookStrategy({
|
||||||
clientID: config.facebook.clientID,
|
clientID: config.facebook.clientID,
|
||||||
clientSecret: config.facebook.clientSecret,
|
clientSecret: config.facebook.clientSecret,
|
||||||
callbackURL: config.serverURL + '/auth/facebook/callback'
|
callbackURL: config.serverURL + '/auth/facebook/callback',
|
||||||
|
state: true,
|
||||||
|
pkce: true
|
||||||
}, passportGeneralCallback))
|
}, passportGeneralCallback))
|
||||||
|
|
||||||
facebookAuth.get('/auth/facebook', function (req, res, next) {
|
facebookAuth.get('/auth/facebook', function (req, res, next) {
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ const githubAuth = module.exports = Router()
|
|||||||
passport.use(new GithubStrategy({
|
passport.use(new GithubStrategy({
|
||||||
clientID: config.github.clientID,
|
clientID: config.github.clientID,
|
||||||
clientSecret: config.github.clientSecret,
|
clientSecret: config.github.clientSecret,
|
||||||
callbackURL: config.serverURL + '/auth/github/callback'
|
callbackURL: config.serverURL + '/auth/github/callback',
|
||||||
|
pkce: true,
|
||||||
|
state: true
|
||||||
}, passportGeneralCallback))
|
}, passportGeneralCallback))
|
||||||
|
|
||||||
githubAuth.get('/auth/github', function (req, res, next) {
|
githubAuth.get('/auth/github', function (req, res, next) {
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ passport.use(new GitlabStrategy({
|
|||||||
clientID: config.gitlab.clientID,
|
clientID: config.gitlab.clientID,
|
||||||
clientSecret: config.gitlab.clientSecret,
|
clientSecret: config.gitlab.clientSecret,
|
||||||
scope: config.gitlab.scope,
|
scope: config.gitlab.scope,
|
||||||
callbackURL: config.serverURL + '/auth/gitlab/callback'
|
callbackURL: config.serverURL + '/auth/gitlab/callback',
|
||||||
|
pkce: true,
|
||||||
|
state: true
|
||||||
}, passportGeneralCallback))
|
}, passportGeneralCallback))
|
||||||
|
|
||||||
gitlabAuth.get('/auth/gitlab', function (req, res, next) {
|
gitlabAuth.get('/auth/gitlab', function (req, res, next) {
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ passport.use(new GoogleStrategy({
|
|||||||
clientID: config.google.clientID,
|
clientID: config.google.clientID,
|
||||||
clientSecret: config.google.clientSecret,
|
clientSecret: config.google.clientSecret,
|
||||||
callbackURL: config.serverURL + '/auth/google/callback',
|
callbackURL: config.serverURL + '/auth/google/callback',
|
||||||
userProfileURL: 'https://www.googleapis.com/oauth2/v3/userinfo'
|
userProfileURL: 'https://www.googleapis.com/oauth2/v3/userinfo',
|
||||||
|
pkce: true,
|
||||||
|
state: true
|
||||||
}, passportGeneralCallback))
|
}, passportGeneralCallback))
|
||||||
|
|
||||||
googleAuth.get('/auth/google', function (req, res, next) {
|
googleAuth.get('/auth/google', function (req, res, next) {
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ const mattermostStrategy = new OAuthStrategy({
|
|||||||
tokenURL: config.mattermost.baseURL + '/oauth/access_token',
|
tokenURL: config.mattermost.baseURL + '/oauth/access_token',
|
||||||
clientID: config.mattermost.clientID,
|
clientID: config.mattermost.clientID,
|
||||||
clientSecret: config.mattermost.clientSecret,
|
clientSecret: config.mattermost.clientSecret,
|
||||||
callbackURL: config.serverURL + '/auth/mattermost/callback'
|
callbackURL: config.serverURL + '/auth/mattermost/callback',
|
||||||
|
state: true
|
||||||
}, passportGeneralCallback)
|
}, passportGeneralCallback)
|
||||||
|
|
||||||
mattermostStrategy.userProfile = (accessToken, done) => {
|
mattermostStrategy.userProfile = (accessToken, done) => {
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ passport.use(new OAuth2CustomStrategy({
|
|||||||
callbackURL: config.serverURL + '/auth/oauth2/callback',
|
callbackURL: config.serverURL + '/auth/oauth2/callback',
|
||||||
userProfileURL: config.oauth2.userProfileURL,
|
userProfileURL: config.oauth2.userProfileURL,
|
||||||
scope: config.oauth2.scope,
|
scope: config.oauth2.scope,
|
||||||
|
pkce: config.oauth2.pkce,
|
||||||
state: true
|
state: true
|
||||||
}, passportGeneralCallback))
|
}, passportGeneralCallback))
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
- Force kill the server after a timeout when waiting for the realtime server to close connections on shutdown
|
- Force kill the server after a timeout when waiting for the realtime server to close connections on shutdown
|
||||||
- Secure iframes with `credentialless` and `sandbox` attributes
|
- Secure iframes with `credentialless` and `sandbox` attributes
|
||||||
- Fix regexes for `[time=...]`, `[name=...]` and `[color=...]` shortcodes in lists
|
- Fix regexes for `[time=...]`, `[name=...]` and `[color=...]` shortcodes in lists
|
||||||
|
- Use `state` parameter for OAuth2 flows and PKCE where applicable
|
||||||
|
|
||||||
## <i class="fa fa-tag"></i> 1.10.3 <i class="fa fa-calendar-o"></i> 2025-04-09
|
## <i class="fa fa-tag"></i> 1.10.3 <i class="fa fa-calendar-o"></i> 2025-04-09
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user