|
| 1 | +# Requirements for Client Applications implementing Workforce (Human) OIDC Auth For MongoDB |
| 2 | + |
| 3 | +# Overview |
| 4 | + |
| 5 | +## Abstract |
| 6 | + |
| 7 | +MongoDB offers OpenID Connect (OIDC) authentication and authorization for database users. OIDC auth in clients generally |
| 8 | +falls into one of two categories: either Workflow OIDC targeting programmatic users, which is |
| 9 | +[fully specified here](https://github.com/mongodb/specifications/blob/master/source/auth/auth.md#mongodb-oidc) and does |
| 10 | +not involve user interaction, or Workforce OIDC targeting human users, which authenticate explicitly through means such |
| 11 | +as browsers. |
| 12 | + |
| 13 | +## Audience |
| 14 | + |
| 15 | +This document is intended for authors and maintainers of MongoDB client applications that implement Workforce OIDC |
| 16 | +authentication, or those who wish to understand existing implementations of it. This document does *not* describe the |
| 17 | +server implementation of OIDC, nor the details of OIDC protocol interactions that are part of external standards and |
| 18 | +generally implemented through the use of a third-party library. |
| 19 | + |
| 20 | +## Meta |
| 21 | + |
| 22 | +The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and |
| 23 | +"OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt). |
| 24 | + |
| 25 | +## Token acquisition flows |
| 26 | + |
| 27 | +Currently, all workforce OIDC clients in MongoDB support one or two mechanisms for acquiring OIDC tokens: |
| 28 | + |
| 29 | +- Authorization Code Flow with PKCE (short: Auth Code, PKCE is pronounced “pixie”) |
| 30 | +- Device Authorization Grant (short: Device Auth) |
| 31 | + |
| 32 | +Auth Code Flow is generally the preferred mechanism. If an application decides to only support one of these flows, that |
| 33 | +should be the typical choice. If an application supports both flows, it MAY default to Auth Code Flow and fall back to |
| 34 | +Device Auth only if |
| 35 | + |
| 36 | +- The former is unavailable under specific circumstances, which are described in the Auth Code Flow section; and |
| 37 | +- The user has explicitly indicated that Device Auth is enabled for this connection attempt. |
| 38 | + |
| 39 | +In both of these cases, the general chain of events is as follows: |
| 40 | + |
| 41 | +1. The user passes a connection string to the client application and indicates (possibly through said connection string) |
| 42 | + that they desire to use `MONGODB-OIDC` as their authentication mechanism. |
| 43 | +2. The application connects to the MongoDB endpoint and asks it for information about the Identity Provider (IdP)[^1] it |
| 44 | + is supposed to authenticate with. If there was a username in the connection string, the application sends that as |
| 45 | + well. |
| 46 | +3. The MongoDB endpoint responds with information about the IdP, in the form of an `issuer` URL identifying the IdP and |
| 47 | + other metadata about it. |
| 48 | +4. The application looks up the public metadata document for the IdP, which includes information about user-facing URL |
| 49 | + endpoints. |
| 50 | +5. The application composes a URL based on those endpoints and presents it to the user in some form (the details depend |
| 51 | + on the exact flow chosen). |
| 52 | +6. The user visits that URL, which can result in any number of steps to authenticate the user. |
| 53 | +7. The application receives a token from the IdP (the details again depend on the exact flow chosen). |
| 54 | +8. The application presents this token to the MongoDB endpoint. |
| 55 | + |
| 56 | +Since in these steps the client application performs actions depending on information supplied by external sources, this |
| 57 | +document specifies mechanisms that help ensure that these actions are carried out securely. MongoDB clients are “public |
| 58 | +clients” in the sense of the OIDC specs (i.e. they do not possess a shared secret with the Identity Provider), and must |
| 59 | +behave accordingly. |
| 60 | + |
| 61 | +Since these steps require the involvement of a human being, the application should be set up to gracefully handle |
| 62 | +timeouts and provide the user with a way to abort the connection attempt. |
| 63 | + |
| 64 | +# General requirements |
| 65 | + |
| 66 | +An application implementing workforce OIDC needs to generally comply with OpenID and OAuth 2.0 standards. |
| 67 | + |
| 68 | +In particular: |
| 69 | + |
| 70 | +- Any HTTP calls made to non-local servers MUST be made using HTTPS. |
| 71 | +- IdP metadata discovery MUST be implemented as described in [RFC8414](https://datatracker.ietf.org/doc/html/rfc8414). |
| 72 | + |
| 73 | +Using a well-tested and standards-compliant third-party library for core OIDC logic for the respective ecosystem is |
| 74 | +highly recommended. If this is not possible, implementers need to pay special attention to the specifications referenced |
| 75 | +in this document. |
| 76 | + |
| 77 | +## Token management |
| 78 | + |
| 79 | +After a successful authentication, applications SHOULD periodically attempt to use the OIDC token refresh mechanism in |
| 80 | +order to exchange the access token it uses for a fresh one. The refresh interval SHOULD be determined by the token |
| 81 | +expiry time indicated by the Identity Provider. If the refresh attempt fails, the application MAY ask or suggest the |
| 82 | +user to re-authenticate from scratch, even if the currently used access token has not expired yet. |
| 83 | + |
| 84 | +Under normal circumstances, the application provides an access token received from the Identity Provider to the driver. |
| 85 | +The application SHOULD provide a toggle to users that can be used to indicate that it should use the ID token, rather |
| 86 | +than the access token, in its place.[^2] |
| 87 | + |
| 88 | +## OIDC Scopes |
| 89 | + |
| 90 | +The initial request to the IdP involves specifying the `scope` parameter. This is a space-separated unordered list of |
| 91 | +strings which contains: |
| 92 | + |
| 93 | +- The scopes listed in the `requestScopes` field of the IdP response from the MongoDB endpoint, and |
| 94 | +- The `openid` and `offline_access` scopes[^3]. If the IdP metadata document contains a `scopes_supported` field, and |
| 95 | + this field does not list one of these scopes, then the respective scope SHOULD NOT be added to the `scope` |
| 96 | + parameter. |
| 97 | + |
| 98 | +# Authorization Code Flow |
| 99 | + |
| 100 | +In this flow, the client application uses a local HTTP server to receive a response from the Identity Provider, as |
| 101 | +described in [RFC8252](https://datatracker.ietf.org/doc/html/rfc8252).[^4] The application follows these steps: |
| 102 | + |
| 103 | +1. Generate a code challenge for PKCE using cryptographically random data, as described in |
| 104 | + [RFC7636](https://datatracker.ietf.org/doc/html/rfc7636). |
| 105 | +2. Launch a local HTTP server. The default (incoming) redirect URL for MongoDB applications is |
| 106 | + `http://localhost:27097/redirect`, which MAY be configurable. If the application allows configuring the URL, the |
| 107 | + port MAY be specified as `0` to allow listening on an arbitrary port. The application listens on the host and port |
| 108 | + listed in the URL. The application MUST listen on all addresses that the hostname resolves to through |
| 109 | + `getaddrinfo()`, and MUST listen on the same port in all cases. If listening on any address fails, or |
| 110 | + `getaddrinfo()` did not return any addresses, it must abort this process and the application MAY fall back to |
| 111 | + Device Auth. |
| 112 | + 1. To preserve privacy, pages served from this local HTTP server MUST NOT reference external resources directly. |
| 113 | + They MAY provide external links that require user interaction. |
| 114 | + 2. The HTTP server SHOULD set default headers of `Content-Security-Policy: default-src 'self'` and |
| 115 | + `Referrer-Policy: no-referrer`, or similarly restrictive defaults. |
| 116 | +3. Compose the URL for Auth Code Flow, using the code challenge generated earlier and the redirect URL from the |
| 117 | + previous step, with the port replaced with the actual port if it was specified as `0`. |
| 118 | + 1. The application MUST add a `state` parameter containing cryptographically random data |
| 119 | + ([RFC6749](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1) specifies this as optional but |
| 120 | + recommended). |
| 121 | + 2. The application SHOULD allow the user to indicate that it should add a `nonce` parameter containing |
| 122 | + cryptographically random data to the authentication request, as defined in the OpenID Connect specification, |
| 123 | + which is then later embedded in the ID token itself. This option SHOULD be on-by-default. (Some Identity |
| 124 | + Providers may effectively require a `nonce` option, but since it is an OpenID Connect feature and not an OAuth |
| 125 | + 2.0 feature, we allow users to disable this if their Identity Provider does not support it.) |
| 126 | +4. The application SHOULD, instead of directly presenting the URL from the previous step to the user, register that URL |
| 127 | + as an outgoing HTTP 307 redirect from the local HTTP server. The redirect base URL SHOULD contain a piece of |
| 128 | + cryptographically random data. The advantages of this approach are that: |
| 129 | + 1. The application can ensure that the URL that is passed to the browser in the next step does not contain special |
| 130 | + characters (other than the typical `/` and `:` present in URLs) which would need to be escaped when opening a |
| 131 | + browser through a shell command. |
| 132 | + 2. It allows tracking whether the URL has been accessed, i.e. get a definite confirmation of whether the user |
| 133 | + successfully opened it in a browser. |
| 134 | +5. Open a browser pointing at that URL, or instruct the user to do so. The former approach is preferred. If opening a |
| 135 | + browser fails, the application MAY fall back to Device Auth. |
| 136 | +6. Wait until one of the following events occurs: |
| 137 | + 1. The local outgoing redirect URL is accessed (if any). |
| 138 | + 2. Opening the browser fails. |
| 139 | + 3. A timeout occurs or the connection attempt is aborted. |
| 140 | +7. Wait until the incoming redirect URL is accessed in a valid way, i.e. using GET or POST with the correct `state` |
| 141 | + parameter and without an `error` parameter. If the incoming redirect URL is accessed with a mismatching `state` |
| 142 | + parameter or the `error` parameter is set, the application SHOULD display a helpful error page. If an `iss` |
| 143 | + parameter is present, it MUST be validated according to [RFC9207](https://datatracker.ietf.org/doc/html/rfc9207), |
| 144 | + i.e. compare equal to the `issuer` string in the issuer metadata document. Subsequent requests to the incoming |
| 145 | + redirect URL should be rejected. |
| 146 | + 1. If error parameters such as `error`, `error_description` and/or `error_uri` are displayed to the user, they MUST |
| 147 | + be validated to match the `NQSCHAR` definition of [RFC6749](https://datatracker.ietf.org/doc/html/rfc6749) (and |
| 148 | + in the case of `error_uri`, a valid URI) before doing so, and they MUST be escaped as appropriate for the |
| 149 | + chosen display format. |
| 150 | +8. The application MUST redirect from the incoming redirect URL to a different URL on the same local HTTP server as a |
| 151 | + HTTP 303 redirect, removing all query parameters and/or the request body. The target page SHOULD indicate |
| 152 | + successful authentication to the Identity Provider, and SHOULD clarify that the user is not authenticated with the |
| 153 | + MongoDB endpoint yet. It MAY be updated once the application successfully authenticates against the MongoDB |
| 154 | + endpoint to reflect that. |
| 155 | +9. Stop listening on and close the local HTTP server. As browsers may keep lingering open connections, the application |
| 156 | + SHOULD ensure that these connections do not prevent the application from progressing to the next step (i.e. do not |
| 157 | + block closing the server). |
| 158 | +10. Verify the parameters it received against the initial PKCE code challenge. |
| 159 | +11. Perform a request to the OIDC token endpoint using the code received from the IdP on the incoming redirect URL. |
| 160 | +12. Provide the token it received from the token endpoint to the MongoDB driver. |
| 161 | + |
| 162 | +The developer tools team owns example templates for the local HTTP server at |
| 163 | +[https://github.com/mongodb-js/devtools-shared/tree/main/packages/oidc-http-server-pages](https://github.com/mongodb-js/devtools-shared/tree/main/packages/oidc-http-server-pages), |
| 164 | +and publishes templates that can be accessed at e.g. |
| 165 | +[https://unpkg.com/@mongodb-js/oidc-http-server-pages/dist/templates.gz](https://unpkg.com/@mongodb-js/oidc-http-server-pages/dist/templates.gz). |
| 166 | + |
| 167 | +# Device Auth Grant |
| 168 | + |
| 169 | +This flow is optional, and MUST NOT be used unless either: |
| 170 | + |
| 171 | +- The application has been unable to open a browser locally (e.g. in headless environments), or unable to listen on a |
| 172 | + local HTTP endpoint, and the user has explicitly indicated that falling back to this flow is acceptable; or |
| 173 | +- The user has explicitly requested this flow. |
| 174 | + |
| 175 | +It is significantly simpler than the Auth Code Flow, but generally considered the less secure one between the two, and |
| 176 | +more susceptible to e.g. phishing attacks. |
| 177 | + |
| 178 | +The application follows these steps: |
| 179 | + |
| 180 | +1. Reach out to the IdP device authorization endpoint. This should result in a URL and a “user code”. |
| 181 | +2. Present the URL and the user code to the user, expecting the user to open that URL manually (possibly on a different |
| 182 | + device). |
| 183 | +3. Repeatedly poll the token endpoint for an access token. |
| 184 | +4. Provide the token it received from the token endpoint to the MongoDB driver. |
| 185 | + |
| 186 | +# Diagnostics |
| 187 | + |
| 188 | +It is recommended to log the following events for diagnostic purposes: |
| 189 | + |
| 190 | +- All outgoing HTTP calls, minus sensitive information (e.g. URLs, with search parameter values redacted) |
| 191 | +- All inbound HTTP calls, minus sensitive information |
| 192 | +- Starting to listen on the local HTTP server, including port and address(es), and whether that was successful or not |
| 193 | +- Incoming redirect to the local HTTP server accessed and whether the redirect was accepted or rejected |
| 194 | +- Closing of the local HTTP server |
| 195 | +- Browser opening and whether it were successful or not |
| 196 | +- Acquisition of new tokens from the Identity Provider, and in particular whether an ID token was present or not |
| 197 | +- Token refresh attempts and whether they were successful or not |
| 198 | + |
| 199 | +# Appendix: Relevant standards (non-exhaustive) |
| 200 | + |
| 201 | +- [RFC6749](https://datatracker.ietf.org/doc/html/rfc6749): The OAuth 2.0 Authorization Framework |
| 202 | +- [RFC6819](https://datatracker.ietf.org/doc/html/rfc6819): OAuth 2.0 Threat Model and Security Considerations |
| 203 | +- [RFC7636](https://datatracker.ietf.org/doc/html/rfc7636): Proof Key for Code Exchange by OAuth Public Clients (PKCE) |
| 204 | +- [RFC8252](https://datatracker.ietf.org/doc/html/rfc8252): OAuth 2.0 for Native Apps |
| 205 | +- [RFC8414](https://datatracker.ietf.org/doc/html/rfc8414): OAuth 2.0 Authorization Server Metadata |
| 206 | +- [RFC8628](https://datatracker.ietf.org/doc/html/rfc8628): OAuth 2.0 Device Authorization Grant |
| 207 | +- [RFC8707](https://datatracker.ietf.org/doc/html/rfc8707): Resource Indicators for OAuth 2.0 |
| 208 | +- [RFC9207](https://datatracker.ietf.org/doc/html/rfc9207): OAuth 2.0 Authorization Server Issuer Identification |
| 209 | +- [RFC9449](https://datatracker.ietf.org/doc/html/rfc9449): OAuth 2.0 Demonstrating Proof of Possession (DPoP) |
| 210 | +- [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html) |
| 211 | +- [Draft](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics): OAuth 2.0 Security Best Current |
| 212 | + Practice |
| 213 | +- [Risk of phishing Access Tokens from clients using OIDC Authentication](https://docs.google.com/document/d/1TdcBtRu4yNXQkI7ZdKWZlSIaWs29tIQblyS3805nK1A/edit?tab=t.0) |
| 214 | + |
| 215 | +# Appendix A: Multiple MongoClients |
| 216 | + |
| 217 | +Some applications may require support for multiple concurrent MongoClients using the same OIDC tokens. In this case, a |
| 218 | +token set (access token, ID token and refresh token) may be re-used, if and only if: |
| 219 | + |
| 220 | +- The `username` parameter is equal |
| 221 | +- The IdP metadata provided by the MongoDB endpoint is equal |
| 222 | +- Both token sets contain an ID token and the respective `aud` and `sub` claims[^5] of the ID tokens are equal, OR |
| 223 | + neither token set contains an ID token |
| 224 | + |
| 225 | +If an application supports multiple MongoClients, it MUST ensure that only one token acquisition flow is in use at the |
| 226 | +same time, and coordinate token refresh intervals accordingly. |
| 227 | + |
| 228 | +The Developer Tools team maintains an implementation that integrates with multiple MongoClient instances at |
| 229 | +[https://github.com/mongodb-js/oidc-plugin](https://github.com/mongodb-js/oidc-plugin), which can be used as a reference |
| 230 | +implementation (and which can generally be used in other applications based on the Node.js driver, although as a |
| 231 | +standalone package it is not considered a supported product of MongoDB). |
| 232 | + |
| 233 | +# Appendix B: Future intentions for endpoint restrictions |
| 234 | + |
| 235 | +Currently, users who connect to a host other than localhost or an Atlas hostname need to specify this host in the |
| 236 | +`ALLOWED_HOSTS` auth mechanism property. In the future, MongoDB is hoping to support Demonstrating Proof of Possession |
| 237 | +(DPoP, [RFC9449](https://datatracker.ietf.org/doc/html/rfc9449)) which will allow lifting this restriction. The goal |
| 238 | +here of either of these mechanisms is to prevent users from connecting to untrusted endpoints that could advertise |
| 239 | +attacker-controlled IdP metadata and intercept tokens intended for other clusters (or even non-MongoDB OIDC |
| 240 | +applications). |
| 241 | + |
| 242 | +We would also like to adopt [RFC8707](https://datatracker.ietf.org/doc/html/rfc8707), but have not decided on a specific |
| 243 | +format for expressing MongoDB clusters as resource URLs. |
| 244 | + |
| 245 | +## Changelog |
| 246 | + |
| 247 | +- 2024-11-14: Initial version. |
| 248 | + |
| 249 | +[^1]: Technically, this refers to an Authorization Server (AS). Inside MongoDB, the usage of AS and IdP has been |
| 250 | + considered more or less interchangeable. |
| 251 | + |
| 252 | +[^2]: At the time of writing, there are Identity Providers which do not support JWT access tokens, but which do provide |
| 253 | + JWT ID tokens that are usable for authentication. This requires opt-in on both the server configuration side and the |
| 254 | + client side. |
| 255 | + |
| 256 | +[^3]: The `offline_access` scope is what provides the ability to refresh tokens without performing a full |
| 257 | + re-authentication. Some particularly security-sensitive customers may choose this approach at the expense of a |
| 258 | + greatly diminished UX, where users need to frequently re-authenticate. |
| 259 | + |
| 260 | +[^4]: [RFC8252](https://datatracker.ietf.org/doc/html/rfc8252) lays out alternatives to this approach, such as registering |
| 261 | + a custom URI scheme handler or registering “Claims” on specific HTTPS URLs, but we’ve decided not to pursue these |
| 262 | + approaches in any existing applications, since some of our applications are CLI applications without an installation |
| 263 | + step where neither of these alternative approaches is feasible. |
| 264 | + |
| 265 | +[^5]: OIDC as a protocol allows for `aud` claims that contain multiple strings, instead of a single one. MongoDB endpoints |
| 266 | + are [restricted](https://jira.mongodb.org/browse/SERVER-86607) to single-value `aud` claims, although client |
| 267 | + applications do not need to be concerned with this restriction. |
0 commit comments