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"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -209,22 +209,23 @@ 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) |
|
||||||
| | `CMD_OAUTH2_USER_PROFILE_EMAIL_ATTR` | **no default**, `email` | where to find the email address in the JSON from the user profile URL. (no default value) |
|
| | `CMD_OAUTH2_USER_PROFILE_EMAIL_ATTR` | **no default**, `email` | where to find the email address in the JSON from the user profile URL. (no default value) |
|
||||||
| | `CMD_OAUTH2_USER_PROFILE_ID_ATTR` | **no default**, `user_uuid` | where to find the dedicated user ID (optional, overrides `CMD_OAUTH2_USER_PROFILE_USERNAME_ATTR`) |
|
| | `CMD_OAUTH2_USER_PROFILE_ID_ATTR` | **no default**, `user_uuid` | where to find the dedicated user ID (optional, overrides `CMD_OAUTH2_USER_PROFILE_USERNAME_ATTR`) |
|
||||||
| | `CMD_OAUTH2_TOKEN_URL` | **no default**, `https://example.com` | sometimes called token endpoint, please refer to the documentation of your OAuth2 provider (no default value) |
|
| | `CMD_OAUTH2_TOKEN_URL` | **no default**, `https://example.com` | sometimes called token endpoint, please refer to the documentation of your OAuth2 provider (no default value) |
|
||||||
| | `CMD_OAUTH2_AUTHORIZATION_URL` | **no default**, `https://example.com` | authorization URL of your provider, please refer to the documentation of your OAuth2 provider (no default value) |
|
| | `CMD_OAUTH2_AUTHORIZATION_URL` | **no default**, `https://example.com` | authorization URL of your provider, please refer to the documentation of your OAuth2 provider (no default value) |
|
||||||
| | `CMD_OAUTH2_CLIENT_ID` | **no default**, `afae02fckafd...` | you will get this from your OAuth2 provider when you register HedgeDoc as OAuth2-client, (no default value) |
|
| | `CMD_OAUTH2_CLIENT_ID` | **no default**, `afae02fckafd...` | you will get this from your OAuth2 provider when you register HedgeDoc as OAuth2-client, (no default value) |
|
||||||
| | `CMD_OAUTH2_CLIENT_SECRET` | **no default**, `afae02fckafd...` | you will get this from your OAuth2 provider when you register HedgeDoc as OAuth2-client, (no default value) |
|
| | `CMD_OAUTH2_CLIENT_SECRET` | **no default**, `afae02fckafd...` | you will get this from your OAuth2 provider when you register HedgeDoc as OAuth2-client, (no default value) |
|
||||||
| | `CMD_OAUTH2_PROVIDERNAME` | **no default**, `My institution` | Optional name to be displayed at login form indicating the oAuth2 provider |
|
| | `CMD_OAUTH2_PROVIDERNAME` | **no default**, `My institution` | Optional name to be displayed at login form indicating the oAuth2 provider |
|
||||||
| | `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