Skip to content

Commit 89f0443

Browse files
authored
Merge pull request #680 from ruby-oauth/feat/e2e-example
2 parents 0c36d66 + ce89ee7 commit 89f0443

File tree

5 files changed

+223
-7
lines changed

5 files changed

+223
-7
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,21 @@ Please file a bug if you notice a violation of semantic versioning.
1818

1919
## [Unreleased]
2020
### Added
21+
- E2E example using mock test server added in v2.0.11
22+
- mock-oauth2-server upgraded to v2.3.0
23+
- https://github.com/navikt/mock-oauth2-server
24+
- `docker compose -f docker-compose-ssl.yml up -d --wait`
25+
- `ruby examples/e2e.rb`
26+
- `docker compose -f docker-compose-ssl.yml down`
27+
- mock server readiness wait is 90s
28+
- override via E2E_WAIT_TIMEOUT
2129
- Apache SkyWalking Eyes dependency license check
2230
### Changed
2331
- Many improvements to make CI more resilient (past/future proof)
2432
### Deprecated
2533
### Removed
2634
### Fixed
35+
2736
### Security
2837

2938
## [2.0.15] - 2025-09-08

README.md

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ OAuth 2.0 focuses on client developer simplicity while providing specific author
2828
desktop applications, mobile phones, and living room devices.
2929
This is a RubyGem for implementing OAuth 2.0 clients (not servers) in Ruby applications.
3030

31-
### Quick Example
31+
### Quick Examples
3232

3333
<details>
3434
<summary>Convert the following `curl` command into a token request using this gem...</summary>
@@ -61,6 +61,61 @@ NOTE: `header` - The content type specified in the `curl` is already the default
6161

6262
</details>
6363

64+
<details>
65+
<summary>Complete E2E single file script against [navikt/mock-oauth2-server](https://github.com/navikt/mock-oauth2-server)</summary>
66+
67+
- E2E example using the mock test server added in v2.0.11
68+
69+
```console
70+
docker compose -f docker-compose-ssl.yml up -d --wait
71+
ruby examples/e2e.rb
72+
# If your machine is slow or Docker pulls are cold, increase the wait:
73+
E2E_WAIT_TIMEOUT=120 ruby examples/e2e.rb
74+
# The mock server serves HTTP on 8080; the example points to http://localhost:8080 by default.
75+
```
76+
77+
The output should be something like this:
78+
79+
```console
80+
ruby examples/e2e.rb
81+
Access token (truncated): eyJraWQiOiJkZWZhdWx0...
82+
userinfo status: 200
83+
userinfo body: {"sub" => "demo-sub", "aud" => ["demo-aud"], "nbf" => 1757816758000, "iss" => "http://localhost:8080/default", "exp" => 1757820358000, "iat" => 1757816758000, "jti" => "d63b97a7-ebe5-4dea-93e6-d542caba6104"}
84+
E2E complete
85+
```
86+
87+
Make sure to shut down the mock server when you are done:
88+
89+
```console
90+
docker compose -f docker-compose-ssl.yml down
91+
```
92+
93+
Troubleshooting: validate connectivity to the mock server
94+
95+
- Check container status and port mapping:
96+
- docker compose -f docker-compose-ssl.yml ps
97+
- From the host, try the discovery URL directly (this is what the example uses by default):
98+
- curl -v http://localhost:8080/default/.well-known/openid-configuration
99+
- If that fails immediately, also try: curl -v --connect-timeout 2 http://127.0.0.1:8080/default/.well-known/openid-configuration
100+
- From inside the container (to distinguish container vs host networking):
101+
- docker exec -it oauth2-mock-oauth2-server-1 curl -v http://127.0.0.1:8080/default/.well-known/openid-configuration
102+
- Simple TCP probe from the host:
103+
- nc -vz localhost 8080 # or: ruby -rsocket -e 'TCPSocket.new("localhost",8080).close; puts "tcp ok"'
104+
- Inspect which host port 8080 is bound to (should be 8080):
105+
- docker inspect -f '{{ (index (index .NetworkSettings.Ports "8080/tcp") 0).HostPort }}' oauth2-mock-oauth2-server-1
106+
- Look at server logs for readiness/errors:
107+
- docker logs -n 200 oauth2-mock-oauth2-server-1
108+
- On Linux, ensure nothing else is bound to 8080 and that firewall/SELinux aren’t blocking:
109+
- ss -ltnp | grep :8080
110+
111+
Notes
112+
- Discovery URL pattern is: http://localhost:8080/<realm>/.well-known/openid-configuration, where <realm> defaults to "default".
113+
- You can change these with env vars when running the example:
114+
- E2E_ISSUER_BASE (default: http://localhost:8080)
115+
- E2E_REALM (default: default)
116+
117+
</details>
118+
64119
If it seems like you are in the wrong place, you might try one of these:
65120

66121
* [OAuth 2.0 Spec][oauth2-spec]

config-ssl.json

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
11
{
22
"interactiveLogin": true,
33
"httpServer": {
4-
"type": "NettyWrapper",
5-
"ssl": {}
6-
}
7-
}
4+
"type": "NettyWrapper"
5+
},
6+
"tokenCallbacks": [
7+
{
8+
"issuerId": "default",
9+
"requestMappings": [
10+
{
11+
"requestParam": "grant_type",
12+
"match": "client_credentials",
13+
"claims": {
14+
"sub": "demo-sub",
15+
"aud": ["demo-aud"]
16+
}
17+
}
18+
]
19+
}
20+
]
21+
}

docker-compose-ssl.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
services:
22
mock-oauth2-server:
3-
image: ghcr.io/navikt/mock-oauth2-server:2.1.11
3+
image: ghcr.io/navikt/mock-oauth2-server:2.3.0
4+
restart: unless-stopped
45
ports:
56
- "8080:8080"
6-
hostname: host.docker.internal
77
volumes:
88
- ./config-ssl.json:/app/config.json:Z
99
environment:

examples/e2e.rb

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# frozen_string_literal: true
2+
3+
# End-to-end example using oauth2 gem against a local mock-oauth2-server.
4+
# Prerequisites:
5+
# 1) Start the mock server (HTTP on 8080):
6+
# docker compose -f docker-compose-ssl.yml up -d --wait
7+
# 2) Run this script:
8+
# ruby examples/e2e.rb
9+
# 3) Stop the server when you're done:
10+
# docker compose -f docker-compose-ssl.yml down
11+
# Notes:
12+
# - The mock server uses a self-signed certificate. SSL verification is disabled in this example.
13+
# - Tested down to Ruby 2.4 (avoid newer syntax).
14+
15+
require "oauth2"
16+
require "json"
17+
require "net/http"
18+
require "uri"
19+
20+
module E2E
21+
class ClientCredentialsDemo
22+
attr_reader :client_id, :client_secret, :issuer_base, :realm
23+
24+
# issuer_base: e.g., https://localhost:8080
25+
# realm: mock-oauth2-server issuer id ("default" by default)
26+
def initialize(client_id, client_secret, issuer_base, realm)
27+
@client_id = client_id
28+
@client_secret = client_secret
29+
@issuer_base = issuer_base
30+
@realm = realm
31+
end
32+
33+
def run
34+
wait_for_server_ready
35+
well_known = discover
36+
token = fetch_token(well_known)
37+
puts "Access token (truncated): #{token.token[0, 20]}..."
38+
call_userinfo(well_known, token)
39+
puts "E2E complete"
40+
end
41+
42+
private
43+
44+
def discovery_url
45+
File.join(@issuer_base, @realm, "/.well-known/openid-configuration")
46+
end
47+
48+
def wait_for_server_ready(timeout = nil)
49+
timeout = (timeout || ENV["E2E_WAIT_TIMEOUT"] || 90).to_i
50+
uri = URI(discovery_url)
51+
deadline = Time.now + timeout
52+
announced = false
53+
loop do
54+
begin
55+
http = Net::HTTP.new(uri.host, uri.port)
56+
http.use_ssl = uri.scheme == "https"
57+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
58+
req = Net::HTTP::Get.new(uri.request_uri)
59+
res = http.request(req)
60+
return if res.code.to_i == 200
61+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ECONNRESET, SocketError, EOFError, OpenSSL::SSL::SSLError
62+
# ignore and retry until timeout
63+
end
64+
unless announced
65+
puts "Waiting for mock OAuth2 server at #{uri} ..."
66+
announced = true
67+
end
68+
break if Time.now >= deadline
69+
sleep(0.5)
70+
end
71+
raise "Server not reachable at #{uri} within #{timeout}s. Ensure it's running: docker compose -f docker-compose-ssl.yml up -d --wait. You can increase the wait by setting E2E_WAIT_TIMEOUT (seconds)."
72+
end
73+
74+
def discover
75+
uri = URI(discovery_url)
76+
http = Net::HTTP.new(uri.host, uri.port)
77+
http.use_ssl = uri.scheme == "https"
78+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
79+
req = Net::HTTP::Get.new(uri.request_uri)
80+
res = http.request(req)
81+
unless res.code.to_i == 200
82+
raise "Discovery failed: #{res.code} #{res.message} - #{res.body}"
83+
end
84+
data = JSON.parse(res.body)
85+
# Expect token_endpoint and possibly userinfo_endpoint
86+
data
87+
end
88+
89+
def fetch_token(well_known)
90+
client = OAuth2::Client.new(
91+
@client_id,
92+
@client_secret,
93+
site: @issuer_base,
94+
token_url: URI.parse(well_known["token_endpoint"]).request_uri,
95+
ssl: {verify: false},
96+
auth_scheme: :request_body, # send client creds in request body (compatible default for mock servers)
97+
)
98+
# Use client_credentials grant
99+
client.client_credentials.get_token
100+
end
101+
102+
def call_userinfo(well_known, token)
103+
userinfo = well_known["userinfo_endpoint"]
104+
unless userinfo
105+
puts "No userinfo_endpoint advertised by server; skipping userinfo call."
106+
return
107+
end
108+
uri = URI(userinfo)
109+
http = Net::HTTP.new(uri.host, uri.port)
110+
http.use_ssl = uri.scheme == "https"
111+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
112+
req = Net::HTTP::Get.new(uri.request_uri)
113+
req["Authorization"] = "Bearer #{token.token}"
114+
res = http.request(req)
115+
puts "userinfo status: #{res.code} #{res.message}"
116+
if res.code.to_i == 200
117+
begin
118+
body = JSON.parse(res.body)
119+
rescue StandardError
120+
body = res.body
121+
end
122+
puts "userinfo body: #{body.inspect}"
123+
else
124+
puts "userinfo error body: #{res.body}"
125+
end
126+
end
127+
end
128+
end
129+
130+
if __FILE__ == $PROGRAM_NAME
131+
# These must match the mock server configuration (see config-ssl.json)
132+
client_id = ENV["E2E_CLIENT_ID"] || "demo-client"
133+
client_secret = ENV["E2E_CLIENT_SECRET"] || "demo-secret"
134+
issuer_base = ENV["E2E_ISSUER_BASE"] || "http://localhost:8080"
135+
realm = ENV["E2E_REALM"] || "default"
136+
137+
E2E::ClientCredentialsDemo.new(client_id, client_secret, issuer_base, realm).run
138+
end

0 commit comments

Comments
 (0)