Design
Current
Architecture
-------------
| user-agent |
-------------
|
V
----|:443|------------------------------
|
V------------|
| |
|------| |
V V V
| ----------------------------
|--->| authn | CI | ... | authz |
V |--------------------------|
|--->| UI | REST API | ... |
| ----------------------------
V A
| |
|---->---->---
Proposed
Architecture
-------------
| user-agent |
-------------
|
V
----|:8080|-----------------------------------------------
|
V
---------------
| API Gateway |
---------------
|
| ---------------------------------
| | IdP (saml, oidc) |
| |-------------------------------|
|--->| :http (authn) | :grpc (authz) |
| ---------------------------------
| A
----------- |
| | |
V V |
------ ------------ |
| UI | | REST API |----------|
------ ------------ |
| A
|---->----------->------------|
[UI]: ui.example.com
[REST API]: api.example.com
[IdP]: idp.example.com
SAML Login Flow
@startuml
Browser -> UI: 1. Get dashboard
UI --> Browser: Generate SAML <AuthnRequest /> and redirect to IdP
Browser -> IdP: 2. Deliver SAML <AuthnRequest />
IdP --> Browser: 3. Redirect to Login Page
Browser -> IdP: 4. Login
IdP --> Browser: 5. Generate SAML <AuthnResponse /> with <Assertion /> and redirect to UI
Browser -> UI: 6. Deliver SAML <AuthnResponse />
UI -> IdP: 7. Exchange <Assertion /> for Tokens
IdP --> UI: Return `access_token` and `refresh_token`
UI --> Browser: Redirect to dashboard
Browser -> UI: Get dashboard
UI -> API: 8. Request list of groups and provide Access Token
API -> IdP: 9. Check if token is valid and check declarative policy
IdP --> API: Return result of `Ability.allowed?`
API --> UI: Return list of groups as JSON
UI --> Browser: Return list of groups as HTML
@enduml
GET http://ui.example.com/saml/newPOST http://idp.example.com/saml/newGET http://idp.example.com/sessions/new?redirect_back=/saml/continuePOST http://idp.example.com/sessionsGET http://idp.example.com/saml/continuePOST http://ui.example.com/saml/assertionsPOST http://idp.example.com/oauth/tokenGET http://api.example.com/groups.jsonGET grpc://idp.example.com/twirp/authx.rpc.Ability/Allowed
OIDC Login Flow
@startuml
Browser -> UI: 1. Get dashboard
UI --> Browser: Generate OAuth Grant Request and redirect to IdP
Browser -> IdP: 2. Deliver OAuth Grant Request
IdP --> Browser: 3. Redirect to Login Page
Browser -> IdP: 4. Login
IdP --> Browser: 5. Generate Consent Screen for Authorization Code flow
Browser -> IdP: 6. Consent
IdP --> Browser: Generate Authorization Code and redirect to UI
Browser -> UI: 7. Deliver Authorization Code Grant
UI -> IdP: 8. Exchange Authorization Code Grant for Tokens
IdP --> UI: Return `access_token` and `refresh_token`
UI --> Browser: Redirect to dashboard
Browser -> UI: Get dashboard
UI -> API: 9. Request list of groups and provide Access Token
API -> IdP: 10. Check if token is valid and check declarative policy
IdP --> API: Return result of `Ability.allowed?`
API --> UI: Return list of groups as JSON
UI --> Browser: Return list of groups as HTML
@enduml
GET http://ui.example.com/oidc/newGET http://idp.example.com/oauth/authorizeGET http://idp.example.com/sessions/new?redirect_back=/oauth/authorize/continuePOST http://idp.example.com/sessionsGET http://idp.example.com/oauth/authorize/continuePOST http://idp.example.com/oauth/authorizeGET http://ui.example.com/oauth/callbackPOST http://idp.example.com/oauth/tokenGET http://api.example.com/groups.jsonGET grpc://idp.example.com/twirp/authx.rpc.Ability/Allowed
Permissions
Option 1
| permission | scope | description |
|---|---|---|
read |
gid://app/Organization/1 |
Can read Org 1 resource |
read |
gid://app/Organization/1/* |
Can read every resource below Org 1 hierarchy |
read |
gid://app/Organization/1/Group/1 |
Can read Group 1 resource |
read |
gid://app/Organization/1/Group/1/* |
Can read every resource below Group 1 hierarchy |
read |
gid://app/Organization/1/Group/1/Project/1 |
Can read project 1 |
read |
gid://app/Project/1 |
Can read project 1 resource (short circuit example) |
read |
gid://app/Organization/1/Group/1?attributes[]=name&attributes[]=description |
Can read name and description of Group 1 resource |
Example:
The following example allows the subject of the token to read all of the descendant resources of Project 1 and Project 2 and it can read Project 3.
{
"sub": "gid://User/17",
"scope": {
"read": [
"gid://app/Organization/1/Group/1/Project/1/*",
"gid://app/Organization/1/Group/1/Project/2/*",
"gid://app/Organization/1/Group/2/Project/3"
]
}
}
Option 2
Encode access and scope directly into the name of the permission.
| permission | description |
|---|---|
read:organization:1 |
Can read Org 1 resource |
read:organization:1:* |
Can read every resource below Org 1 hierarchy |
read:organization:1:group:* |
Can read Group 1 resource |
read:organization:1:group:1:* |
Can read every resource below Group 1 hierarchy |
read:organization:1:group:1:project:1 |
Can read project 1 |
read:project:1 |
Can read project 1 resource (short circuit example) |
read:organization:1:group:1:attributes[]=name&attributes[]=description |
Can read name and description of Group 1 resource |
Example:
{
"sub": "gid://User/17",
"scope": [
"read:organization:1:group:1:project:1:*",
"read:organization:1:group:1:project:2:*",
"read:organization:1:group:2:project:3"
]
}