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 Object/Params | Response Object/Params | Notes |
---|---|---|---|---|---|
GET | /oauth2/clients/{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 | The web layer can use this to get details about a client requesting authorization. |
POST | /oauth2/clients | 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/on Supplemental params | Supplemental params could include the URL to a logo, link to a website for the app, terms of service, etc. 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/clients/{id} | Delete a client | Path param: id: an existing OAuth2 Client ID | None | |
GET | /oauth2/auth | 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 |
POST | /oauth2/auth | 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 Body: LoginRequest (already exists) | If login is successful: Redirect URL: redirect_uri (provided in request) Parameters: code: the authorization code state: the same value in the request Should we include a LoginResponse body? (Probably not, if the body is kept in the redirect then we may be exposing a session token to the 3rd party client) | 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/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 (or make our own) if we want to, but there is probably no need. More info. |
POST | /oauth2/token or /oauth2/token/refresh | Called by a client to refresh an authentication 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 | |
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: 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 | We must have this if we decide to not include scope with the access token |
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. |
...
What other potentially crucial information about Hydra can we find on the internet?
Decisions and requirements to deploy via Cloudformation
See:
Jira Legacy | ||||||
---|---|---|---|---|---|---|
|
Internal configuration
We should tailor ORY Hydra's configuration to meet our needs
Database
Per the ORY Hydra docs:
The SQL adapter supports two DBMS: PostgreSQL 9.6+ and MySQL 5.7+. Please note that older MySQL versions have issues with ORY Hydra's database schema. For more information go here.
If my understanding is correct, the DB that Hydra uses is entirely separate from other services, so it should not be a concern here that Synapse currently uses MySQL 5.6
One concern here is that ORY Hydra requirements may evolve to conflict with other constraints. For example, Hydra may have a security flaw that is only patched by upgrading to a database service that is not provided by Amazon RDS.
Infrastructure Configuration
For reliability, we will want to deploy two instances of ORY Hydra behind a load balancer.
Is Hydra truly stateless? Can we safely configure it behind a load balancer?
How does Hydra "federate" identity providers? Should we configure Hydra differently to interact with Synapse u:p vs. Synapse users that use Google SSO via OAuth? Or delegate that complexity to a Synapse Authentication provider?
Choosing an ELB type
AWS offers three different types of load balancers, described further in this AWS document.
- Application Load Balancer
- Network Load Balancer
- Classic Load Balancer (formerly Elastic Load Balancer)
Also worth looking into (if relevant?) is the Elastic Container Service.
VPC
Per the ORY Hydra docs, ORY Hydra has two ports, a public port, and an administrative port. I think the VPC/ELB should forward requests over TLS/443 to the public port.
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
When researching Spring OAuth support, one must be careful to discern between the core Spring Security module (in which OAuth2 implementation is currently in progress in version 5), and the Spring Security OAuth module (which is in "maintenance mode" (which does not support OIDC, is not receiving feature updates, and I imagine is likely to be deprecated some time after Spring Security 5 is feature-complete).
This blog post outlines plans to implement OAuth2 in future versions of Spring with a high-level roadmap.
I think this uses the old version of Spring Security:
This question seems to outline what we want to do:
https://stackoverflow.com/questions/52683165/creating-oauth-2-0-login-provider-with-spring-boot
...
ORY Hydra authentication + consent flow
See the user guide
Hydra stores its own authentication sessions (this would be a redundancy in Synapse). Additionally, the Hydra documentation instructs implementers to not utilize session in the authentication provider (I think we probably could if we wanted to, but we would want to be thorough in our understanding of how Hydra handles sessions in these cases).
- An OAuth client will redirect a user to a page controlled by the Hydra server to authenticate the user.
- If the user has an existing Hydra authentication session go to #,
- With no existing authentication session, the user is redirected to an authentication provider (i.e. Synapse login) to enter and credentials.
- The authentication provider will authenticate the user and communicate info about the user directly to Hydra
- The user is redirected to Hydra for authorization, which will redirect the user to the consent provider (i.e. Synapse).
- The consent provider will ask the user for permission to grant access to the requested resources and will directly tell Hydra if the user accepts or rejects the request.
- The user is redirected to the OAuth client. Hydra handles access code and token generation.
Decisions and requirements to deploy via Cloudformation
See:
Jira Legacy | ||||||
---|---|---|---|---|---|---|
|
Internal configuration
We should tailor ORY Hydra's configuration to meet our needs
Database
Per the ORY Hydra docs:
The SQL adapter supports two DBMS: PostgreSQL 9.6+ and MySQL 5.7+. Please note that older MySQL versions have issues with ORY Hydra's database schema. For more information go here.
If my understanding is correct, the DB that Hydra uses is entirely separate from other services, so it should not be a concern here that Synapse currently uses MySQL 5.6
One concern here is that ORY Hydra requirements may evolve to conflict with other constraints. For example, Hydra may have a security flaw that is only patched by upgrading to a database service that is not provided by Amazon RDS.
Infrastructure Configuration
For reliability, we will want to deploy two instances of ORY Hydra behind a load balancer.
Is Hydra truly stateless? Can we safely configure it behind a load balancer?
How does Hydra "federate" identity providers? Should we configure Hydra differently to interact with Synapse u:p vs. Synapse users that use Google SSO via OAuth? Or delegate that complexity to a Synapse Authentication provider?
Choosing an ELB type
AWS offers three different types of load balancers, described further in this AWS document.
- Application Load Balancer
- Network Load Balancer
- Classic Load Balancer (formerly Elastic Load Balancer)
Also worth looking into (if relevant?) is the Elastic Container Service.
VPC
Per the ORY Hydra docs, ORY Hydra has two ports, a public port, and an administrative port. I think the VPC/ELB should forward requests over TLS/443 to the public port.
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
When researching Spring OAuth support, one must be careful to discern between the core Spring Security module (in which OAuth2 implementation is currently in progress in version 5), and the Spring Security OAuth module (which is in "maintenance mode" (which does not support OIDC, is not receiving feature updates, and I imagine is likely to be deprecated some time after Spring Security 5 is feature-complete).
This blog post outlines plans to implement OAuth2 in future versions of Spring with a high-level roadmap.
I think this uses the old version of Spring Security:
This question seems to outline what we want to do:
https://stackoverflow.com/questions/52683165/creating-oauth-2-0-login-provider-with-spring-boot
OIDC is a layer on top of OAuth, why can we not just implement it on top of the old version of Spring Security?
...
code
token
id_token
id_token token
code id_token
code token
code id_token token
none
The old version of Spring Security was not built to handle this. Here is the issue (which has not been resolved at the time of writing: https://github.com/spring-projects/spring-security-oauth/issues/619)
The answerer of the SO post also has a blog post that goes more in-depth: https://medium.com/@darutk/full-scratch-implementor-of-oauth-and-openid-connect-talks-about-findings-55015f36d1c3
So
...
in theory you could develop something OIDC-like on top of the existing infrastructure, but it won't be spec-compliant, which is a considerable drawback for a service designed for external services that are not supported by Synapse engineers.
So how would we use Spring Security?
First we need to make sure it can handle all of our needs. Development Spring Security 5 development on OAuth2+OIDC support is ongoingincomplete (as of Oct 2018), so it isn't guaranteed that it can currently do what we need it to.
This features matrix shows the state of OAuth2 support in Spring Security 5 (and compares it to Spring Security OAuth 2, the old version). Note that this has not been updated since Jan 2018, and I suspect it is out of date.
https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Features-Matrix
Of note:
...
Of note:
What is the future of OAuth 2.0 support in Spring Security?
The next generation of OAuth 2.0 support is currently underway in Spring Security 5, as we introduced new Client support for the OAuth 2.0 Authorization Framework and OpenID Connect Core 1.0. The plan is to also provide support for Resource Server by mid-2018 and Authorization Server by the end of 2018 or early 2019 along with more extensive support for OAuth 2.0 Core and Extensions, OpenID Connect 1.0 and Javascript Object Signing and Encryption (JOSE).
Are there new features being implemented in Spring Security OAuth 2.2+?
We will provide bug/security fixes and consider adding minor features but we will not be adding major features. Our plan going forward is to build all the features currently in Spring Security OAuth into Spring Security 5.x. After Spring Security has reached feature parity with Spring Security OAuth, we will continue to support bugs and security fixes for at least one year.
Here are some SpringBoot examples that we could probably leverage:
OAuth2 Authorization Server: https://github.com/spring-projects/spring-security/tree/5.1.1.RELEASE/samples/boot/oauth2authorizationserver
OAuth2 Resource Server: https://github.com/spring-projects/spring-security/tree/5.1.1.RELEASE/samples/boot/oauth2resourceserver
The Spring consider looking at. They are built into the current version of Spring Security but they use the old Spring Security OAuth module.
The Spring Security 5.2 Docs only outline configuring an OAuth2 Resource Server, which simplifies validation of that tokens from an Authorization server. It's not immediately clear if that buys us anything, since we have to write our own Authorization and Token-issuing service anyways (the use cases they give involve using a federated authorization server, e.g. Okta).
The (old) Spring Security OAuth2 supports creating an OAuth2 Authorization server. If we decide we never need OIDC (or we are content with potentially having to rewrite this component later), then I believe this will work fine (assuming the module will receive maintenance for at least a few more years to come, then I believe this will work fine (assuming the module will receive maintenance for at least a few more years to come.
Can we use Spring Security to manage tokens?
Depends on which module we use (Security 5 vs OAuth module)
Spring Security 5
The OAuth2 Resource Server supports decoding JWT tokens. There is no token generation system implemented at the time of writing (Oct 2018)
Spring Security OAuth
To some extent:
When creating your
AuthorizationServerTokenServices
implementation, you may want to consider using theDefaultTokenServices
which has many strategies that can be plugged in to change the format and storage of access tokens. By default it creates tokens via random value and handles everything except for the persistence of the tokens which it delegates to aTokenStore
. The default store is an in-memory implementation, but there are some other implementations available. Here's a description with some discussion of each of them
The
JdbcTokenStore
is the JDBC version of the same thing, which stores token data in a relational database. Use the JDBC version if you can share a database between servers, either scaled up instances of the same server if there is only one, or the Authorization and Resources Servers if there are multiple components. To use theJdbcTokenStore
you need "spring-jdbc" on the classpath.The JSON Web Token (JWT) version of the store encodes all the data about the grant into the token itself (so no back end store at all which is a significant advantage). One disadvantage is that you can't easily revoke an access token, so they normally are granted with short expiry and the revocation is handled at the refresh token. Another disadvantage is that the tokens can get quite large if you are storing a lot of user credential information in them. The
JwtTokenStore
is not really a "store" in the sense that it doesn't persist any data, but it plays the same role of translating betweeen token values and authentication information in theDefaultTokenServices
.
There is no provided JDBC schema because it is designed to be configurable for the specific use case, but they have examples available.
Another library to look into: Connect2ID's OAuth2.0 SDK with OpenID Connect
Spring Security OAuth actually uses this internally
https://connect2id.com/products/nimbus-oauth-openid-connect-sdk
...