Skip to content

Commit 9f1e44a

Browse files
committed
Adds dynamic client registration how-to guide
Closes gh-647
1 parent 0a87e78 commit 9f1e44a

File tree

5 files changed

+234
-0
lines changed

5 files changed

+234
-0
lines changed

docs/modules/ROOT/nav.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@
1111
** xref:guides/how-to-userinfo.adoc[]
1212
** xref:guides/how-to-jpa.adoc[]
1313
** xref:guides/how-to-custom-claims-authorities.adoc[]
14+
** xref:guides/how-to-dynamic-client-registration.adoc[]
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
[[how-to-dynamic-client-registration]]
2+
= How-to: Register a client dynamically
3+
:index-link: ../how-to.html
4+
:docs-dir: ..
5+
6+
This guide shows how to configure OpenID Connect Dynamic Client Registration 1.0 in Spring Authorization Server and
7+
walks through an example of how to register a client. Spring Authorization Server implements https://openid.net/specs/openid-connect-registration-1_0.html[OpenID Connect Dynamic Client Registration 1.0]
8+
specification, gaining the ability to dynamically register and retrieve OpenID clients.
9+
10+
- xref:guides/how-to-dynamic-client-registration.adoc#enable[Enable Dynamic Client Registration in Spring Authorization Server]
11+
- xref:guides/how-to-dynamic-client-registration.adoc#configure-initial-client[Configure initial client]
12+
- xref:guides/how-to-dynamic-client-registration.adoc#fetch-initial-access-token[Fetch initial access token]
13+
- xref:guides/how-to-dynamic-client-registration.adoc#register-client[Register a client]
14+
- xref:guides/how-to-dynamic-client-registration.adoc#fetch-client[Fetch client]
15+
16+
[[enable]]
17+
== Enable Dynamic Client Registration in Spring Authorization Server
18+
19+
By default, dynamic client registration functionality is turned off in Spring Authorization Server.
20+
To enable, add the following configuration:
21+
22+
[[sample.dcrAuthServerConfig]]
23+
[source,java]
24+
----
25+
include::{examples-dir}/main/java/sample/dcr/DcrConfiguration.java[]
26+
----
27+
28+
<1> Add a `SecurityFilterChain` `@Bean` that registers an `OAuth2AuthorizationServerConfigurer`
29+
<2> In the configurer, apply OIDC client registration endpoint customizer with default values.
30+
This enables dynamic client registration functionality.
31+
32+
Please refer to xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[Client Registration Endpoint docs] for
33+
in-depth configuration details.
34+
35+
[[configure-initial-client]]
36+
== Configure initial client
37+
38+
An initial client is required in order to register new clients in the authorization server. The client must be configured
39+
with scopes `client.create` and optionally `client.read` for creating clients and reading clients, respectively.
40+
A programmatic example of such a client is below.
41+
42+
[[sample.dcrRegisteredClientConfig]]
43+
[source,java]
44+
----
45+
include::{examples-dir}/main/java/sample/dcr/RegisteredClientConfiguration.java[]
46+
----
47+
48+
<1> A `RegisteredClientRepository` `@Bean` is configured with a set of clients.
49+
<2> An initial client with client id `initial-client` is configured.
50+
<3> `client_credentials` grant type is set to fetch access tokens directly.
51+
<4> `client.create` scope is configured for the client to ensure they are able to create clients.
52+
<5> `client.read` scope is configured for the client to ensure they are able to fetch and read clients.
53+
<6> The initial client is saved into the data store.
54+
55+
After configuring the above, run the authorization server in your preferred environment.
56+
57+
[[fetch-initial-access-token]]
58+
== Fetch initial access token
59+
60+
An initial access token is required to be able to create client registration requests. The token request must contain a
61+
request for scope `client.create` only.
62+
63+
[source,console]
64+
----
65+
curl -X POST --location "https://authserver.example.org/oauth2/token" --http1.1 \
66+
-H "Authorization: Basic <base64-encoded-credentials>" \
67+
-H "Content-Type: application/x-www-form-urlencoded" \
68+
-d "grant_type=client_credentials&scope=client.create"
69+
----
70+
71+
[WARNING]
72+
====
73+
If you provide more than one scope in the request, you will not be able to register a client. The client creation
74+
request requires an access token with a single scope of `client.create`
75+
====
76+
77+
[TIP]
78+
====
79+
To obtain encoded credentials for the above request, `base64` encode the client credentials in the format of
80+
`<clientId>:<clientSecret>`. Below is an encoding operation for the example in this guide.
81+
82+
[source,console]
83+
----
84+
echo -n "initial-app:secret" | base64
85+
----
86+
====
87+
88+
[[register-client]]
89+
== Register a client
90+
91+
With an access token obtained from the previous step, register a new client with the following request.
92+
93+
[NOTE]
94+
The access token can only be used once. After a single registration request, the access token is invalidated.
95+
96+
[source,console]
97+
----
98+
curl -X POST --location "https://authserver.example.org/connect/register" --http1.1 \
99+
-H "Content-Type: application/json" \
100+
-H "Accept: application/json" \
101+
-H "Authorization: Bearer <initial-access-token>" \
102+
-d "{
103+
\"client_name\": \"My Example\",
104+
\"grant_types\": [
105+
\"authorization_code\",
106+
\"client_credentials\",
107+
\"refresh_token\"
108+
],
109+
\"scope\": \"openid profile email\",
110+
\"redirect_uris\": [
111+
\"https://client.example.org/callback\",
112+
\"https://client.example.org/callback2\"
113+
],
114+
\"token_endpoint_auth_method\": \"client_secret_basic\",
115+
\"post_logout_redirect_uris\": [
116+
\"https://client.example.org/logout\"
117+
]
118+
}"
119+
----
120+
121+
An example register client response may be as follows:
122+
123+
[source,console]
124+
----
125+
HTTP/1.1 201
126+
...
127+
128+
{
129+
"client_id": "Q_AQ0wUzbTXo-IE4rNJbU9Dv8BBex1zQrjDeJs0mDbM",
130+
"client_id_issued_at": 1690726915,
131+
"client_name": "My Example",
132+
"client_secret": "XleADJhomxA2Rmyom2hmpnS6_CDnyAFBI9JsGeC-XQ0QLa9p9JExXJABiYz7fOXA",
133+
"redirect_uris": [
134+
"https://client.example.org/callback",
135+
"https://client.example.org/callback2"
136+
],
137+
"post_logout_redirect_uris": [
138+
"https://client.example.org/logout"
139+
],
140+
"grant_types": [
141+
"refresh_token",
142+
"client_credentials",
143+
"authorization_code"
144+
],
145+
"response_types": [
146+
"code"
147+
],
148+
"scope": "openid profile email",
149+
"token_endpoint_auth_method": "client_secret_basic",
150+
"id_token_signed_response_alg": "RS256",
151+
"registration_client_uri": "https://authserver.example.org/connect/register?client_id=Q_AQ0wUzbTXo-IE4rNJbU9Dv8BBex1zQrjDeJs0mDbM",
152+
"registration_access_token": "<access-token>",
153+
"client_secret_expires_at": 0
154+
}
155+
----
156+
157+
With the client registered, a `registration_access_token` and a `registration_client_uri` are provided to be able to
158+
read the created client in a follow up request. The next step is optional.
159+
160+
[[fetch-client]]
161+
== Fetch client
162+
163+
Using fields `registration_access_token` and `registration_client_uri` from the previous step's response, read the client
164+
with the following request:
165+
166+
[source,console]
167+
----
168+
curl -X GET --location "<registration_client_uri>" \
169+
-H "Authorization: Bearer <registration_access_token>" \
170+
-H "Accept: application/json"
171+
----
172+
173+
The response should contain the same information about the client as seen when the client was first registered, with
174+
the exception of `registration_access_token` field.

docs/modules/ROOT/pages/how-to.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@
1212
* xref:guides/how-to-userinfo.adoc[Customize the OpenID Connect 1.0 UserInfo response]
1313
* xref:guides/how-to-jpa.adoc[Implement core services with JPA]
1414
* xref:guides/how-to-custom-claims-authorities.adoc[Add authorities as custom claims in JWT access tokens]
15+
* xref:guides/how-to-dynamic-client-registration.adoc[Register a client dynamically]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package sample.dcr;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.security.config.Customizer;
6+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
7+
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
8+
import org.springframework.security.web.SecurityFilterChain;
9+
10+
@Configuration
11+
public class DcrConfiguration {
12+
@Bean // <1>
13+
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
14+
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
15+
new OAuth2AuthorizationServerConfigurer();
16+
http.apply(authorizationServerConfigurer);
17+
18+
authorizationServerConfigurer
19+
.oidc(oidc ->
20+
oidc.clientRegistrationEndpoint(Customizer.withDefaults()) // <2>
21+
);
22+
23+
return http.build();
24+
}
25+
26+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package sample.dcr;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.jdbc.core.JdbcTemplate;
6+
import org.springframework.security.oauth2.core.AuthorizationGrantType;
7+
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
8+
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
9+
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
10+
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
11+
12+
import java.util.UUID;
13+
14+
@Configuration
15+
public class RegisteredClientConfiguration {
16+
@Bean // <1>
17+
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
18+
RegisteredClient initialClient = RegisteredClient.withId(UUID.randomUUID().toString())
19+
.clientId("initial-client") // <2>
20+
.clientSecret("{noop}secret")
21+
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
22+
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) // <3>
23+
.scope("client.create") // <4>
24+
.scope("client.read") // <5>
25+
.build();
26+
27+
JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
28+
registeredClientRepository.save(initialClient); // <6>
29+
30+
return registeredClientRepository;
31+
}
32+
}

0 commit comments

Comments
 (0)