forked from spring-projects/spring-authorization-server
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds dynamic client registration how-to guide
Closes spring-projectsgh-647
- Loading branch information
Showing
4 changed files
with
233 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
174 changes: 174 additions & 0 deletions
174
docs/modules/ROOT/pages/guides/how-to-dynamic-client-registration.adoc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
[[how-to-dynamic-client-registration]] | ||
= How-to: Register a client dynamically | ||
:index-link: ../how-to.html | ||
:docs-dir: .. | ||
|
||
This guide shows how to configure OpenID Connect Dynamic Client Registration 1.0 in Spring Authorization Server and | ||
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] | ||
specification, gaining the ability to dynamically register and retrieve OpenID clients. | ||
|
||
- xref:guides/how-to-dynamic-client-registration.adoc#enable[Enable Dynamic Client Registration in Spring Authorization Server] | ||
- xref:guides/how-to-dynamic-client-registration.adoc#configure-initial-client[Configure initial client] | ||
- xref:guides/how-to-dynamic-client-registration.adoc#fetch-initial-access-token[Fetch initial access token] | ||
- xref:guides/how-to-dynamic-client-registration.adoc#register-client[Register a client] | ||
- xref:guides/how-to-dynamic-client-registration.adoc#fetch-client[Fetch client] | ||
|
||
[[enable]] | ||
== Enable Dynamic Client Registration in Spring Authorization Server | ||
|
||
By default, dynamic client registration functionality is turned off in Spring Authorization Server. | ||
To enable, add the following configuration: | ||
|
||
[[sample.dcrAuthServerConfig]] | ||
[source,java] | ||
---- | ||
include::{examples-dir}/main/java/sample/dcr/DcrConfiguration.java[] | ||
---- | ||
|
||
<1> Add a `SecurityFilterChain` `@Bean` that registers an `OAuth2AuthorizationServerConfigurer` | ||
<2> In the configurer, apply OIDC client registration endpoint customizer with default values. | ||
This enables dynamic client registration functionality. | ||
|
||
Please refer to xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[Client Registration Endpoint docs] for | ||
in-depth configuration details. | ||
|
||
[[configure-initial-client]] | ||
== Configure initial client | ||
|
||
An initial client is required in order to register new clients in the authorization server. The client must be configured | ||
with scopes `client.create` and optionally `client.read` for creating clients and reading clients, respectively. | ||
A programmatic example of such a client is below. | ||
|
||
[[sample.dcrRegisteredClientConfig]] | ||
[source,java] | ||
---- | ||
include::{examples-dir}/main/java/sample/dcr/RegisteredClientConfiguration.java[] | ||
---- | ||
|
||
<1> A `RegisteredClientRepository` `@Bean` is configured with a set of clients. | ||
<2> An initial client with client id `initial-client` is configured. | ||
<3> `client_credentials` grant type is set to fetch access tokens directly. | ||
<4> `client.create` scope is configured for the client to ensure they are able to create clients. | ||
<5> `client.read` scope is configured for the client to ensure they are able to fetch and read clients. | ||
<6> The initial client is saved into the data store. | ||
|
||
After configuring the above, run the authorization server in your preferred environment. | ||
|
||
[[fetch-initial-access-token]] | ||
== Fetch initial access token | ||
|
||
An initial access token is required to be able to create client registration requests. The token request must contain a | ||
request for scope `client.create` only. | ||
|
||
[source,console] | ||
---- | ||
curl -X POST --location "https://authserver.example.org/oauth2/token" --http1.1 \ | ||
-H "Authorization: Basic <base64-encoded-credentials>" \ | ||
-H "Content-Type: application/x-www-form-urlencoded" \ | ||
-d "grant_type=client_credentials&scope=client.create" | ||
---- | ||
|
||
[WARNING] | ||
==== | ||
If you provide more than one scope in the request, you will not be able to register a client. The client creation | ||
request requires an access token with a single scope of `client.create` | ||
==== | ||
|
||
[TIP] | ||
==== | ||
To obtain encoded credentials for the above request, `base64` encode the client credentials in the format of | ||
`<clientId>:<clientSecret>`. Below is an encoding operation for the example in this guide. | ||
[source,console] | ||
---- | ||
echo -n "initial-app:secret" | base64 | ||
---- | ||
==== | ||
|
||
[[register-client]] | ||
== Register a client | ||
|
||
With an access token obtained from the previous step, register a new client with the following request. | ||
|
||
[NOTE] | ||
The access token can only be used once. After a single registration request, the access token is invalidated. | ||
|
||
[source,console] | ||
---- | ||
curl -X POST --location "https://authserver.example.org/connect/register" --http1.1 \ | ||
-H "Content-Type: application/json" \ | ||
-H "Accept: application/json" \ | ||
-H "Authorization: Bearer <initial-access-token>" \ | ||
-d "{ | ||
\"client_name\": \"My Example\", | ||
\"grant_types\": [ | ||
\"authorization_code\", | ||
\"client_credentials\", | ||
\"refresh_token\" | ||
], | ||
\"scope\": \"openid profile email\", | ||
\"redirect_uris\": [ | ||
\"https://client.example.org/callback\", | ||
\"https://client.example.org/callback2\" | ||
], | ||
\"token_endpoint_auth_method\": \"client_secret_basic\", | ||
\"post_logout_redirect_uris\": [ | ||
\"https://client.example.org/logout\" | ||
] | ||
}" | ||
---- | ||
|
||
An example register client response may be as follows: | ||
|
||
[source,console] | ||
---- | ||
HTTP/1.1 201 | ||
... | ||
{ | ||
"client_id": "Q_AQ0wUzbTXo-IE4rNJbU9Dv8BBex1zQrjDeJs0mDbM", | ||
"client_id_issued_at": 1690726915, | ||
"client_name": "My Example", | ||
"client_secret": "XleADJhomxA2Rmyom2hmpnS6_CDnyAFBI9JsGeC-XQ0QLa9p9JExXJABiYz7fOXA", | ||
"redirect_uris": [ | ||
"https://client.example.org/callback", | ||
"https://client.example.org/callback2" | ||
], | ||
"post_logout_redirect_uris": [ | ||
"https://client.example.org/logout" | ||
], | ||
"grant_types": [ | ||
"refresh_token", | ||
"client_credentials", | ||
"authorization_code" | ||
], | ||
"response_types": [ | ||
"code" | ||
], | ||
"scope": "openid profile email", | ||
"token_endpoint_auth_method": "client_secret_basic", | ||
"id_token_signed_response_alg": "RS256", | ||
"registration_client_uri": "https://authserver.example.org/connect/register?client_id=Q_AQ0wUzbTXo-IE4rNJbU9Dv8BBex1zQrjDeJs0mDbM", | ||
"registration_access_token": "<access-token>", | ||
"client_secret_expires_at": 0 | ||
} | ||
---- | ||
|
||
With the client registered, a `registration_access_token` and a `registration_client_uri` are provided to be able to | ||
read the created client in a follow up request. The next step is optional. | ||
|
||
[[fetch-client]] | ||
== Fetch client | ||
|
||
Using fields `registration_access_token` and `registration_client_uri` from the previous step's response, read the client | ||
with the following request: | ||
|
||
[source,console] | ||
---- | ||
curl -X GET --location "<registration_client_uri>" \ | ||
-H "Authorization: Bearer <registration_access_token>" \ | ||
-H "Accept: application/json" | ||
---- | ||
|
||
The response should contain the same information about the client as seen when the client was first registered, with | ||
the exception of `registration_access_token` field. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package sample.dcr; | ||
|
||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.security.config.Customizer; | ||
import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; | ||
import org.springframework.security.web.SecurityFilterChain; | ||
|
||
@Configuration | ||
public class DcrConfiguration { | ||
@Bean // <1> | ||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { | ||
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = | ||
new OAuth2AuthorizationServerConfigurer(); | ||
http.apply(authorizationServerConfigurer); | ||
|
||
authorizationServerConfigurer | ||
.oidc(oidc -> | ||
oidc.clientRegistrationEndpoint(Customizer.withDefaults()) // <2> | ||
); | ||
|
||
return http.build(); | ||
} | ||
|
||
} |
32 changes: 32 additions & 0 deletions
32
docs/src/main/java/sample/dcr/RegisteredClientConfiguration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package sample.dcr; | ||
|
||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.jdbc.core.JdbcTemplate; | ||
import org.springframework.security.oauth2.core.AuthorizationGrantType; | ||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod; | ||
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; | ||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; | ||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; | ||
|
||
import java.util.UUID; | ||
|
||
@Configuration | ||
public class RegisteredClientConfiguration { | ||
@Bean // <1> | ||
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) { | ||
RegisteredClient initialClient = RegisteredClient.withId(UUID.randomUUID().toString()) | ||
.clientId("initial-client") // <2> | ||
.clientSecret("{noop}secret") | ||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) | ||
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) // <3> | ||
.scope("client.create") // <4> | ||
.scope("client.read") // <5> | ||
.build(); | ||
|
||
JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate); | ||
registeredClientRepository.save(initialClient); // <6> | ||
|
||
return registeredClientRepository; | ||
} | ||
} |