Skip to content

Commit

Permalink
chore: poc hydra
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicolas Burtey committed Jun 4, 2023
1 parent d6cb79b commit cc16259
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 7 deletions.
22 changes: 22 additions & 0 deletions dev/ory/hydra.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
serve:
cookies:
same_site_mode: Lax

urls:
self:
issuer: http://127.0.0.1:4444
consent: http://127.0.0.1:3000/consent
login: http://127.0.0.1:3000/login
logout: http://127.0.0.1:3000/logout

secrets:
system:
- youReallyNeedToChangeThis

oidc:
subject_identifiers:
supported_types:
- pairwise
- public
pairwise:
salt: youReallyNeedToChangeThis
8 changes: 7 additions & 1 deletion dev/ory/oathkeeper.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ authenticators:
jwks_urls:
- https://firebaseappcheck.googleapis.com/v1beta/jwks
- file:///home/ory/jwks.json # ONLY FOR DEV, DO NOT USE IN PRODUCTION

bearer_token:
enabled: true
config:
Expand All @@ -27,6 +28,11 @@ authenticators:
subject_from: identity.id
extra_from: identity.traits

oauth2_introspection:
enabled: true
config:
introspection_url: http://hydra:4445/admin/oauth2/introspect

anonymous:
enabled: true
config:
Expand Down Expand Up @@ -54,7 +60,7 @@ mutators:
config:
jwks_url: file:///home/ory/jwks.json
issuer_url: "galoy.io"
claims: '{"sub": "{{ print .Subject }}" }'
claims: '{"sub": "{{ print .Subject }}", card: "{{ print .Ext.card }}" }'

noop:
enabled: true
Expand Down
3 changes: 3 additions & 0 deletions dev/ory/oathkeeper_rules.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
preserve_query: true
subject_from: identity.id
extra_from: identity.traits
- handler: oauth2_introspection
config:
introspection_url: http://hydra:4445/admin/oauth2/introspect
- handler: bearer_token
config:
check_session_url: http://kratos:4433/sessions/whoami
Expand Down
4 changes: 4 additions & 0 deletions docker-compose.override.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,7 @@ services:
fulcrum:
ports:
- "50001:50001"
hydra:
ports:
- "4444:4444" # Public port
- "4445:4445" # Admin port
40 changes: 40 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ services:
- otel-agent
- oathkeeper
- mailslurper
- hydra
# - consent
restart: on-failure:10
integration-deps:
image: busybox
Expand Down Expand Up @@ -371,3 +373,41 @@ services:
- SSL_CERTFILE=/tls.cert
- SSL_KEYFILE=/tls.key
command: ["Fulcrum", "/fulcrum.conf"]
hydra:
image: oryd/hydra:v2.1.2
command: serve -c /home/ory/hydra.yml all --dev
volumes:
- type: bind
source: dev/ory
target: /home/ory
environment:
- DSN=postgres://hydra:secret@postgresdhydra:5432/hydra?sslmode=disable&max_conns=20&max_idle_conns=4
restart: unless-stopped
depends_on:
- hydra-migrate
- postgresdhydra
hydra-migrate:
image: oryd/hydra:v2.1.2
environment:
- DSN=postgres://hydra:secret@postgresdhydra:5432/hydra?sslmode=disable&max_conns=20&max_idle_conns=4
command: migrate -c /home/ory/hydra.yml sql -e --yes
volumes:
- type: bind
source: dev/ory
target: /home/ory
restart: on-failure
depends_on:
- postgresdhydra
# consent:
# environment:
# - HYDRA_ADMIN_URL=http://hydra:4445
# image: oryd/hydra-login-consent-node:v2.1.2
# ports:
# - "3000:3000"
# restart: unless-stopped
postgresdhydra:
image: postgres:14.1
environment:
- POSTGRES_USER=hydra
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=hydra
116 changes: 116 additions & 0 deletions docs/hydra.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@

Make sure you have `hydra` command line installed

```sh
brew install ory-hydra
```

# run the experiment:

Follow the instructions below


On console 1:

launch the hydra login consent node, which will provide the authentication (interactive with kratos API) and consent page.

```sh
hydra-login-consent-node % yarn start
```

On console 2:
```sh
galoy % make start-deps
```

On console 3:
Follow the instructions below


## create a OAuth2 client

Think of the client as the service that need to get the delegation access

If you use concourse currently, you'll login with Google Workspace.

When you login through concourse you'll use google workspace. The client is concourse in this example.

For the galoy stack, some examples of clients could be Alby, a boltcard service, a nostr wallet connect service, an accountant that access the wallet in read only.


```sh
code_client=$(hydra create client \
--endpoint http://127.0.0.1:4445 \
--grant-type authorization_code,refresh_token \
--response-type code,id_token \
--format json \
--scope openid --scope offline \
--redirect-uri http://127.0.0.1:5555/callback
)

code_client_id=$(echo $code_client | jq -r '.client_id')
code_client_secret=$(echo $code_client | jq -r '.client_secret')
```

## Initiate the request

this simulate the front end client.
would be mobile app for adding a boltcard

```sh
hydra perform authorization-code \
--client-id $code_client_id \
--client-secret $code_client_secret \
--endpoint http://127.0.0.1:4444/ \
--port 5555 \
--scope openid --scope offline
```

do the login and consent

copy the Access token to the mobile app.

you are now connect through Hydra.


### limitations

as both settings in Oathkeeper:

```
- handler: oauth2_introspection
config:
introspection_url: http://hydra:4445/admin/oauth2/introspect
```

and

```
- handler: bearer_token
config:
check_session_url: http://kratos:4433/sessions/whoami
preserve_path: true
preserve_query: true
subject_from: identity.id
extra_from: identity.traits
```

rely on Authorization header, currency if hydra fails for the oauth2_introspection, Oathkeeper return an Unauthorized response

so the bearer_token is not be tested.

To mitigate this issue, either:
- Hydra token should have a different header (not Authorization) than Kratos token
- All requests should come through Hydra

### debug

hydra introspect token \
--format json-pretty \
--endpoint http://127.0.0.1:4445/ \
TOKEN
# OR
curl -X POST http://localhost:4445/admin/oauth2/introspect -d token=ory_at_TOKEN

curl -I -X POST http://localhost:4456/decisions/graphql -H 'Authorization: Bearer ory_at_TOKEN'

10 changes: 8 additions & 2 deletions src/app/auth/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ import { sendOathkeeperRequest } from "@services/oathkeeper"
import jwksRsa from "jwks-rsa"
import jsonwebtoken from "jsonwebtoken"

type LoginWithPhoneTokenResult = {
authToken: SessionToken
id: UserId
}

export const loginWithPhoneToken = async ({
phone,
code,
Expand All @@ -53,7 +58,7 @@ export const loginWithPhoneToken = async ({
phone: PhoneNumber
code: PhoneCode
ip: IpAddress
}): Promise<SessionToken | ApplicationError> => {
}): Promise<LoginWithPhoneTokenResult | ApplicationError> => {
{
const limitOk = await checkFailedLoginAttemptPerIpLimits(ip)
if (limitOk instanceof Error) return limitOk
Expand Down Expand Up @@ -87,7 +92,8 @@ export const loginWithPhoneToken = async ({
} else if (kratosResult instanceof Error) {
return kratosResult
}
return kratosResult.sessionToken

return { authToken: kratosResult.sessionToken, id: kratosResult.kratosUserId }
}

export const loginWithPhoneCookie = async ({
Expand Down
1 change: 1 addition & 0 deletions src/graphql/admin/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ scalar AuthToken
type AuthTokenPayload {
authToken: AuthToken
errors: [Error!]!
id: ID!
}

"""
Expand Down
1 change: 1 addition & 0 deletions src/graphql/main/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ scalar AuthToken
type AuthTokenPayload {
authToken: AuthToken
errors: [Error!]!
id: ID!
}

"""
Expand Down
10 changes: 6 additions & 4 deletions src/graphql/root/mutation/user-login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,19 @@ const UserLoginMutation = GT.Field<{
return { errors: [{ message: "ip is undefined" }] }
}

const authToken = await Auth.loginWithPhoneToken({
const res = await Auth.loginWithPhoneToken({
phone,
code,
ip,
})

if (authToken instanceof Error) {
return { errors: [mapAndParseErrorForGqlResponse(authToken)] }
if (res instanceof Error) {
return { errors: [mapAndParseErrorForGqlResponse(res)] }
}

return { errors: [], authToken }
const { authToken, id } = res

return { errors: [], authToken, id }
},
})

Expand Down
3 changes: 3 additions & 0 deletions src/graphql/types/payload/auth-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ const AuthTokenPayload = GT.Object({
authToken: {
type: AuthToken,
},
id: {
type: GT.NonNull(GT.ID),
},
}),
})

Expand Down

0 comments on commit cc16259

Please sign in to comment.