Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

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.

...

POSTclientsCreate a

OAuth2Client

name
VerbEndpointPurposeRequestResponseNotes
GET/oauth2/clientsclient/{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

Necessary? May not need thisOnly the owner or a Synapse admin can make this request.
GET/oauth2/client/List clients created by user
List of above.Don't return secret.
POST/oauth2/clientCreate 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/on

Supplemental 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/clientsclient/{id}Delete a client

Path param:

id: an existing OAuth2 Client ID

None
PUT/oauth2/clientsclient/{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.

...

VerbEndpointPurposeRequest Object/ParamsResponse Object/ParamsNotes
POST/login/scopedGet 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).

GET/oauth2/detailsGet human-interpretable

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/detailsGet 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/consentThe 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 login is successfulscopedAccessToken is valid:

Body: Redirect

URLOAuthClientUrl: 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.

POST/oauth2/revoke

A logged in user can revoke OAuth2 client access using this method.

OAuth2RevokeRequestOAuth2RevokeRequest

client_id: unique

Is there a need for more granularity?

None

Revoking access not in the OAuth2 spec but allowing users to revoke client access may be important.

Token Requests

...

  Revocation should be at the token level not at the client level.

Token Requests

These endpoints would be used by OAuth2.0 clients to retrieve tokens with an access code

VerbEndpointPurposeRequestResponse

Notes

POST/oauth2/tokenCalled 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)

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.The token type in almost all OAuth2 cases is   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/refreshCalled 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.

Web Layer Interfaces (Portal)

...

OpenID Connect services


VerbEndpointPurposeRequestResponseNotes
GET
/.well-known/openid-configuration
return metadata about the serviceN/A

See spec -→

Key elements are:

https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRespon
GET/oauth2/userinforeturn information about the userN/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/userinfoas aboveN/Aas aboveAlthough 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)

The portal needs to implement interfaces for handling the components of OAuth2 that are best accomplished through user interfaces.

...

Diagrams to show where these API endpoints would be used and what objects/params are needed:

Image RemovedImage Added



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

...