Table of Contents |
---|
This page is starting as a collection of notes, design decisions, etc. related to implementing OAuth2 into Synapse. Part of the process has included considerations about developing our own library, or using an off-the-shelf solution like ORY Hydra. The information on this page may change as the project evolves.
...
Verb | Endpoint | Purpose | Request | Response | Notes | ||
---|---|---|---|---|---|---|---|
GET | /oauth2/client/{id} | Get details about one client | Path param: id: an existing OAuth2 Client ID | OAuth2Client name: String redirect_uri: String client_id: Unique created_by/on modified_by/on Supplemental params | If Only the owner makes or a Synapse admin can make this request he also gets the client secret back. | ||
GET | /oauth2/client | Create a client | / | List clients created by user | List of above. | Don't return secret. | |
POST | /oauth2/client | Create a client | OAuth2Client name: String redirect_uri: String Supplemental params | OAuth2Client name: String redirect_uri: String client_id: Unique client_secret: String created_by/on modified_by/onSupplemental params | Supplemental params could include the URL to a logo, link to a website for the app, terms of service, etc. See https://openid.net/specs/openid-connect-registration-1_0.html Typically the secret key is used to perform these actions, but if we give Synapse users a claim over an OAuth2 client, we could use their credentials. | ||
DELETE | /oauth2/client/{id} | Delete a client | Path param: id: an existing OAuth2 Client ID | None | |||
PUT | /oauth2/client/{id} | Update a client | OAuth2Client name: String redirect_uri: String Supplemental params | OAuth2Client name: String redirect_uri: String client_id: Unique created_by/on modified_by/on Supplemental params | Typically the secret key is used to perform these actions, but if we give Synapse users a claim over an OAuth2 client, we could use their credentials. |
...
Verb | Endpoint | Purpose | Request Object/Params | Response Object/Params | Notes |
---|---|---|---|---|---|
POST | /login/scoped | Get a scoped access token | sessionToken: String scope: String | scopedLoginResponse: scopedSessionToken: String acceptsTermsOfUse: Boolean scope: String exp: Integer (seconds until expiry) | This is a more secure alternative to the current session token as limits what can be done with the session token. These can (should?) expire quickly (minutes-hours). This could be done by passing in username and password rather than sessionToken, but we'd have to handle the case where the user has no password and logs in via Google (or other OAuth provider). |
GET | /oauth2/details | Get human-interpretable details about the requesting client, and the scope that they are requesting | Parameters clientId: Unique (the ID of an existing OAuth2 client requesting access) scope: String | OAuth2Client scopes: Array<string> e.g. [("read", "syn123"), ("create","syn456")] (actual representation TBD) | The web layer can use this to get details about a client requesting authorization and the scope they request |
POST | /oauth2/consent | The user grants access to the OAuth2 Client to access protected resources | URL Parameters: response_type: String (always "code") client_id: Unique redirect_uri: String (points to OAuth client) scope: String state: String | If scopedAccessToken is valid: Body: OAuthClientUrl: String redirect_uri?code={code}&state={state} (all provided in request) Parameters: code: the authorization code state: the same value in the request | Who should execute this? The User Agent or the Web Layer on behalf of the user agent? Question: how to handle with various Synapse IdPs? (E.g. Synapse users who sign in with Google accounts). The "state" parameter is designed to avoid CSRF attacks and the client must utilize it per RFC-6749 § 10.12. More info. |
| O
|
|
...
Verb | Endpoint | Purpose | Request | Response | Notes |
---|---|---|---|---|---|
POST | /oauth2/token | Called by a client to get an access token | Body: OAuth2AuthorizationCodeTokenRequest grant_type: String (always "authorization_code" for this call) code: String (the authorization code) redirect_uri: String (should be the same as previous redirect uri) client_id: Unique client_secret: String | Body: OAuth2AccessToken access_token: String token_type: String ("Bearer") expires_in: Integer (seconds) refresh_token: String (optionally, scope)Authorization | codes must be single-use and short-lived. If an authorization code is As per OIDC the response should include both an access token and an ID Token, https://openid.net/specs/openid-connect-core-1_0.html#TokenResponse Authorization codes must be single-use and short-lived. If an authorization code is used more than once, we should revoke the active access token retrieved with the code. More info @ RFC-6749 § 10.5 The redirect URI should be validated here before granting a token, along with the credentials in the request. More info @ RFC-6749 § 10.6. The token should be opaque/unguessable. More info @ RFC-6749 § 10.3./unguessable. More info @ RFC-6749 § 10.3. Update: token could be JWT, https://www.oauth.com/oauth2-servers/access-tokens/self-encoded-access-tokens/. Advantage is that permissions are 'built in' to the token. Downside is that it becomes irrevocable. So let's not use JWT. The token type in almost all OAuth2 cases is "Bearer". We can use a different token type (e.g. HMAC, or make our own) if we want to. See RFC-6749 § 7.1. Additional info. |
POST | /oauth2/token/refresh | Called by a client to refresh an access token | Body: OAuth2AuthorizationCodeRefreshTokenRequest grant_type: String (always "refresh_token" for this call) refresh_token: String client_id: Unique client_secret: String | Body: OAuth2AccessToken access_token: String token_type: String ("Bearer") expires_in: Integer (seconds) refresh_token: String | Client authentication must be done here. This must be done over TLS. More info @ RFC-6749 § 10.4 |
POST | /oauth2/token/introspect or /oauth2/token/info | Clients can determine if an authentication token is valid (and get scope, if it is opaque in the token) | Body: OAuth2TokenIntrospectionRequest token: String client_id: Unique client_secret: String | Body: OAuth2TokenIntrospectionResponse active: Boolean client_id: Unique username: String (principal of user who authorized the token) exp: Date (seconds until expiration) scope: Array<String> (human-interpretable scope) | This endpoint is not strictly necessary, but we should strongly consider including this if we decide to not include scope with the access token. See RFC-7662. |
OpenID Connect services
Verb | Endpoint | Purpose | Request | Response | Notes |
---|---|---|---|---|---|
GET | /.well-known/openid-configuration | return metadata about the service | N/A | See spec - |
→ Key elements are:
| https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRespon | ||||
GET | /oauth2/userinfo | return information about the user | N/A | Body: sub: "subject", Synapse user id aud: "audience", the OAuth client ID iat: "issued at", the timestamp when the response was created given_name: first name family_name: last name | https://openid.net/specs/openid-connect-basic-1_0.html#UserInfo Note: content type must be application/jwt as per https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse |
POST | /oauth2/userinfo | as above | N/A | as above | Although GET is the recommended HTTP method, POST must be supported, as per https://openid.net/specs/openid-connect-core-1_0.html#UserInfo |
Web Layer Interfaces (Portal)
...
Diagrams to show where these API endpoints would be used and what objects/params are needed:
How do we know if we're up-to-spec when we are done?
...
The administrative port should not be exposed to public internet traffic. If you want to expose certain endpoints, such as the
/clients
endpoint for OpenID Connect Dynamic Client Registry, you can do so but you need to properly secure these endpoints with an API Gateway or Authorization Proxy.
Do we need this?
Spring Security
...