Table of Contents |
---|
...
- Let third party (web) applications securely access a user’s data in Synapse. Today such applications must either
- predownload/embed data,
- use the application author’s Synapse credentials, or
- prompt the user for their Synapse credentials
- Let a headless batch job (e.g.,a “workflow”) securely access a user’s data in Synapse. Today such a process must either
- Use predownloaded data
- Use the job runner’s Synapse credentials
Brief Overview of OAuth2
...
This document presupposes a basic (not necessarily thorough) level of understanding of OAuth2. There are four authorization grant flows in the OAuth spec. They can be summarized:
- Authorization Code grant (most secure, client secret confidentiality must be guaranteed)
- Upon user consent, an OAuth client (3rd party) is granted an authorization code
- The authorization code can be used with a client secret to obtain a scoped access token.
- The access token can be used to access resources until it expires or is revoked
- The access token can be refreshed by the client with a refresh token and the client secret.
- Implicit code grant (client secret confidentiality cannot be guaranteed)
- Upon user consent, an OAuth client (3rd party) is granted a scoped access token.
- The token can be used to access resources until it is expired or revoked, but it cannot be refreshed. The time that the token is active is typically very short (minutes).
- Resource owner password credentials (not secure, especially with an untrusted client)
- The user provides their username and password to the OAuth client
- The OAuth client uses the credentials to obtain a scoped access token
- Client credentials (used for cases where clients manage their own resources, i.e. not really authorization delegation)
- The OAuth client can request an access token with their client ID and client secret
...
These endpoints are necessary for users to approve/reject OAuth2.0 access requests
Verb | Endpoint | Purpose | Request Object/Params | Response Object/Params | Notes |
---|
POST | / |
login/ |
Parameters
clientId: Unique (the ID of an existing OAuth2 client requesting access)scoped | Get a scoped access token | sessionToken: String scope: String |
OAuth2Client
scopes: Array<string>
e.g. [("read", "syn123"), ("create","syn456")]
(actual representation TBD)
Display the login/consent info to the user and prompt for an accept/reject
URL Parameters:
response_type: String (always "code")
client_id: Unique
redirect_uri: String (points to OAuth client)
scope: String
state: String
Web interface for Synapse authorization
The form should permit login and we must be able to include the request parameters in a new request
This endpoint should point to a web layer that can show a UI with a login form and display the access that the user can consent to, along with a prompt for the user to accept/reject.
We should think about using login cookies here to simplify the UX if a user is already logged into Synapse
URL Parameters:
response_type: String (always "code")
client_id: Unique
redirect_uri: String (points to OAuth client)
scope: String
state: String
If login is successful:
Redirect URL:
redirect_uri (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. More info.
A logged in user can revoke OAuth2 client access using this method.
OAuth2RevokeRequest
client_id: unique
Is there a need for more granularity?
Revoking access not in the OAuth2 spec but allowing users to revoke client access may be important.
Token Requests
These endpoints would be used by OAuth2.0 clients to retrieve tokens with an access code
Body:
OAuth2AuthorizationCodeTokenRequestscopedLoginResponse: 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/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 login is successful: Redirect URL: redirect_uri (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. More info. |
POST | /oauth2/revoke | A logged in user can revoke OAuth2 client access using this method. | OAuth2RevokeRequest 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
These endpoints would be used by OAuth2.0 clients to retrieve tokens with an access code
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) | The redirect URI should be validated here before granting a token, along with the credentials in the request. More info. 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, but there is probably no need. More info. |
POST | /oauth2/token/refresh | Called by a client to refresh an access token | Body: OAuth2AuthorizationCodeRefreshTokenRequest grant_type: String (always " |
refresh_ |
code: String (the authorization code)
redirect_uri: String (should be the same as previous redirect uri)token" for this call) refresh_token: String client_id: Unique client_secret: String | Body: OAuth2AccessToken access_ |
token |
: String |
expires_in: Integer (seconds)
refresh_token: String
(optionally, scope)
The redirect URI should be validated here before granting a token, along with the credentials in the request. More info.token |
_type: String ("Bearer") expires_in: Integer (seconds) refresh_token: String | |
GET | /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: |
OAuth2AuthorizationCodeRefreshTokenRequest
grant_type: String (always "refresh_token" for this call)
OAuth2TokenIntrospectionRequest token: String client_id: Unique client_secret: String | Body: |
OAuth2AccessToken
access_token: String
token_type: String ("Bearer")
expires_in: Integer (seconds)
refresh_token: String
/oauth2/token/introspect
or
/oauth2/token/info
Body:
OAuth2TokenIntrospectionRequest
token: String
client_id: Unique
client_secret: String
Body:
OAuth2TokenIntrospectionResponse
active: Boolean
client_id: Unique
username: String (principal of user who authorized)
exp: Date (seconds until expiration)
scope: String
Web Layer
OAuth2TokenIntrospectionResponse active: Boolean client_id: Unique username: String (principal of user who authorized) 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 |
Web Layer Interfaces (Portal)
The portal needs to implement interfaces for handling the components of OAuth2 that are best accomplished through user interfaces.
Page | Purpose | Actions |
---|---|---|
OAuth2 Authentication | Provides an interface for the user to authenticate in order to manage OAuth2 requests | Prompt for authentication (u:p/OAuth login) Retrieve a scoped access token on behalf of the user (which can only be used to authorize OAuth2 authorization requests) Redirect to/render OAuth2.0 Authorization Request |
OAuth2 Authorization | Provides an interface for the user to approve/reject OAuth requests | Retrieve/interpret client details + scope and display to the user "Do you want Client123 to have full access over syn123" Approve/reject OAuth2 request on behalf of the user Redirect user-agent to OAuth2AuthorizedUrl (provided by backend) |
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?
...
If we do not implement OIDC (just OAuth2) there doesn't seem to be any certification process that I can find. We may have to read the spec ourselves and hope we don't miss anything with thorough tests, future security audits, etc.
What is "scope"?
JIRAs(?):
Jira Legacy | ||||||||
---|---|---|---|---|---|---|---|---|
|
...
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
...