Run the IdP
From the cloned repository:
$ cd bikeshare-security/idp
$ ./gradlew bootRun
This starts on port 9000. Open http://localhost:9000/.well-known/oauth-authorization-server: this is the Authorization Server’s metadata document, the same kind any OAuth2 client (or resource server) fetches to discover the token endpoint, the authorization endpoint, and the key used to verify tokens, instead of hardcoding them.
Where the clients are registered
Open idp/src/main/resources/application.yaml.
There is no Java code defining the two clients: Spring Boot’s Authorization Server auto-configuration reads them straight from spring.security.oauth2.authorizationserver.client.
Match what you see there to the previous page:
-
bikeshare-frontendhas noclient-secret,client-authentication-methods: [none], andrequire-proof-key: true: a public client, PKCE is mandatory instead of a secret. -
bike-sensorhas aclient-secretandclient-authentication-methods: [client_secret_basic]: a confidential client, it authenticates like a regular HTTP Basic client. -
Each lists only the scopes it is allowed to request, and a short
access-token-time-to-live.
The only things not driven by this file are a demo user to log in as, and CORS; see SecurityConfig.java for those, and Log in from the frontend for why CORS is needed at all.
Get a token and read it
The sensor’s Client Credentials grant needs no browser, so it is the easiest one to try from a terminal:
$ curl -u bike-sensor:sensor-secret \
-d grant_type=client_credentials -d scope=telemetry:write \
http://localhost:9000/oauth2/token
Copy the access_token value, and decode its payload (the middle, base64url-encoded segment between the two dots) without sending it anywhere:
$ echo '<paste the segment between the first and second dot>' | python3 -c "
import sys, base64, json
s = sys.stdin.read().strip()
s += '=' * (-len(s) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(s)), indent=2))
"
You should see iss (the issuer, matching the metadata document), exp/iat (expiry and issued-at), aud (here, the client itself), and scope.
Predict, then try:
-
Request
scope=bikes:readinstead (a scopebike-sensoris not allowed). The IdP rejects the whole request withinvalid_scope: unlike the backend’s per-endpoint checks you will write next, the IdP refuses to even issue a token for a scope the client was never registered for. -
Omit
-d scope=telemetry:writeentirely. A token is still issued, but decode it: there is noscopeclaim at all. Keep this in mind for the sensor script — a token with no scope claim satisfies nohasAuthority("SCOPE_…")check.
|
Note
|
The discovery document above is deliberately the plain OAuth2 one.
Our IdP also exposes the OIDC-flavored /.well-known/openid-configuration, since SecurityConfig enables .oidc(…), but neither of our clients requests the openid scope, so neither ever receives an identity (id_token): we are only using OAuth2 for authorization here, not OIDC for authentication.
A real frontend that needed to display "logged in as Jane" would add openid profile to its scopes.
|