diff --git a/docs/docs/examples/introduction.mdx b/docs/docs/examples/introduction.mdx index 7ae7ab8924..6587334735 100644 --- a/docs/docs/examples/introduction.mdx +++ b/docs/docs/examples/introduction.mdx @@ -101,7 +101,7 @@ Our examples cover a range of programming languages and frameworks, so no matter Java Spring Boot Web - + Guide @@ -194,7 +194,7 @@ Our examples cover a range of programming languages and frameworks, so no matter Java Spring Boot API - + Guide diff --git a/docs/docs/examples/login/java-spring.md b/docs/docs/examples/login/java-spring.md new file mode 100644 index 0000000000..22a60c06cc --- /dev/null +++ b/docs/docs/examples/login/java-spring.md @@ -0,0 +1,177 @@ +--- +title: ZITADEL with Java Spring Boot +sidebar_label: Java Spring Boot +--- + +This integration guide demonstrates the recommended way to incorporate ZITADEL into your Spring Boot web application. +It explains how to enable user login in your application and how to fetch data from the user info endpoint. + +By the end of this guide, your application will have login functionality and will be able to access the current user's profile. + +:::info +This documentation references our [example](https://github.com/zitadel/zitadel-java) on GitHub. +You can either create your own application or directly run the example by providing the necessary arguments. +::: + +## Set up application + +Before we begin developing our application, we need to perform a few configuration steps in the ZITADEL Console. +You'll need to provide some information about your app. We recommend creating a new app to start from scratch. Navigate to your Project, then add a new application at the top of the page. +Select the **Web** application type and continue. + +![Create app in console](/img/java-spring/app-create.png) + +We recommend that you use [Proof Key for Code Exchange (PKCE)](/apis/openidoauth/grant-types#proof-key-for-code-exchange) for all applications. + +![Create app in console - set auth method](/img/java-spring/app-create-auth.png) + +### Redirect URIs + +The Redirect URIs field tells ZITADEL where it's allowed to redirect users after authentication. For development, you can set dev mode to `true` to enable insecure HTTP and redirect to a `localhost` URI. +The Post-logout redirect send the users back to a route on your application after they have logged out. + +:::info +If you are following along with the [example](https://github.com/zitadel/zitadel-java), set the dev mode to `true`, the Redirect URIs to `http://localhost:18080/webapp/login/oauth2/code/zitadel` and Post redirect URI to `http://localhost:18080/webapp`. +::: + +![Create app in console - set redirectURI](/img/java-spring/app-create-redirect.png) + +Continue and create the application. + +### Client ID + +After successful creation of the app, a pop-up will appear displaying the app's client ID. Copy the client ID, as you will need it to configure your Java client. + +![Create app in console - copy client_id](/img/java-spring/app-create-clientid.png) + +## Spring setup + +Now that you have configured your web application on the ZITADEL side, you can proceed with the integration of your Spring client. +This guide will reference the [example repository](https://github.com/zitadel/zitadel-java) and explain the necessary steps taken in there. +If your starting from scratch, you can use the Spring Initializer with the [following setup](https://start.spring.io/#!type=maven-project&language=java&platformVersion=3.2.1&packaging=jar&jvmVersion=17&dependencies=web,thymeleaf,security,oauth2-client,lombok) as a base. + +### Support classes + +To be able to take the most out of ZITADELs RBAC, we first need to create a GrantedAuthoritiesMapper, that will map the role claims (`urn:zitadel:iam:org:project:roles`) +into Spring Security `authiorities`, which can be used later on to determine the granted permissions. + +So in your application, create a 'support/zitadel' package and in there the `ZitadelGrantedAuthoritiesMapper.java`: + +```java reference +https://github.com/zitadel/zitadel-java/blob/main/web/src/main/java/demo/support/zitadel/ZitadelGrantedAuthoritiesMapper.java +``` + +The following two classes will provide you the possibility to access and use the user's token for requests to another API, e.g. the Spring Boot API example. + +Directly create them in the `support` package. + +```java reference +https://github.com/zitadel/zitadel-java/blob/main/web/src/main/java/demo/support/TokenAccessor.java +``` + +```java reference +https://github.com/zitadel/zitadel-java/blob/main/web/src/main/java/demo/support/AccessTokenInterceptor.java +``` + +### Application server configuration + +As we have now our support classes, we can now create and configure the application server (and API client) itself. + +In a new `config` package, create first the `WebClientConfig.java`, which will provide a RestTemplate using the previously created AccessTokenInterceptor: + +```java reference +https://github.com/zitadel/zitadel-java/blob/main/web/src/main/java/demo/config/WebClientConfig.java +``` + +Additionally also create the `WebSecurityConfig.java` in the same package. This class will take care of the authentication, redirecting the user to the login, +mapping the claims (using the ZitadelGrantedAuthoritiesMapper) and also provide the possibility for logout: + +```java reference +https://github.com/zitadel/zitadel-java/blob/main/web/src/main/java/demo/config/WebSecurityConfig.java +``` + +For the authentication (and the server in general) to work, the application needs some configuration, so please provide the following to your `application.yml` (resources folder): + +```yaml reference +https://github.com/zitadel/zitadel-java/blob/main/web/src/main/resources/application.yml +``` + +Note that both the `issuer-uri` as well as the `client-id` are only placeholders. You can either change them in here using the values provided by ZITADEL +or pass them later on as arguments when starting the application. + +### Add pages to your application + +To be able to serve these pages create a `templates` directory in the `resources` folder. +Now create three HTML files in the new `templates` folder and copy the content of the examples: + +**index.html** + +The home page will display the Userinfo from the authentication context and the granted roles / Spring security authorities. + +```html reference +https://github.com/zitadel/zitadel-java/blob/main/web/src/main/resources/templates/index.html +``` + +**fragments.html** + +The navigation to switch between the home and tasks page and allows the user to logout. + +```html reference +https://github.com/zitadel/zitadel-java/blob/main/web/src/main/resources/templates/fragments.html +``` + +**tasks.html** + +The tasks page allows to interact with the Spring Boot API example and display / add new tasks. + +```html reference +https://github.com/zitadel/zitadel-java/blob/main/web/src/main/resources/templates/tasks.html +``` + +**UiController** + +To serve these pages and handler their actions, you finally need a `UiController.java`: + +```java reference +https://github.com/zitadel/zitadel-java/blob/main/web/src/main/java/demo/web/UiController.java +``` + +### Start your application + +In case you've created your own application and depending on your development setup you might need to build the application first: + +```bash +mvn clean package -DskipTests +``` + +You will need to provide the `issuer-uri` (your ZITADEL domain) and the `client-id` previously created: + +```bash +java \ + -Dspring.security.oauth2.client.provider.zitadel.issuer-uri= \ + -Dspring.security.oauth2.client.registration.zitadel.client-id= \ + -jar web/target/web-0.0.2-SNAPSHOT.jar +``` + +This could look like: + +```bash +java \ + -Dspring.security.oauth2.client.provider.zitadel.issuer-uri=https://my-domain.zitadel.cloud \ + -Dspring.security.oauth2.client.registration.zitadel.client-id=243861220627644836@example \ + -jar web/target/web-0.0.2-SNAPSHOT.jar +``` + +If you then visit on you should directly be redirected to your ZITADEL instance. +After login with your existing user you will be presented the profile page: + +![Profile Page](/img/java-spring/app-profile.png) + +## Completion + +Congratulations! You have successfully integrated your Spring Boot web application with ZITADEL! + +If you get stuck, consider checking out our [example](https://github.com/zitadel/zitadel-java) application. +This application includes all the functionalities mentioned in this quickstart. +You can directly start it with your own configuration. If you face issues, contact us or raise an issue on [GitHub](https://github.com/zitadel/zitadel-java/issues). + diff --git a/docs/docs/examples/secure-api/java-spring.md b/docs/docs/examples/secure-api/java-spring.md new file mode 100644 index 0000000000..c545f13c25 --- /dev/null +++ b/docs/docs/examples/secure-api/java-spring.md @@ -0,0 +1,283 @@ +--- +title: ZITADEL with Java Spring Boot +sidebar_label: Java Spring Boot +--- + +This integration guide shows you how to integrate **ZITADEL** into your Java Spring Boot API. It demonstrates how to secure your API using +OAuth 2 Token Introspection. + +At the end of the guide you should have an API with a protected endpoint. + +:::info +This documentation references our [example](https://github.com/zitadel/zitadel-java) on GitHub. +You can either create your own application or directly run the example by providing the necessary arguments. +::: + +## Set up application + +Before we begin developing our API, we need to perform a few configuration steps in the ZITADEL Console. +You'll need to provide some information about your app. We recommend creating a new app to start from scratch. Navigate to your Project, then add a new application at the top of the page. +Select the **API** application type and continue. + +![Create app in console](/img/java-spring/api-create.png) + +Select Basic Auth for authenticating at the Introspection Endpoint. + +![Create app in console](/img/java-spring/api-create-auth.png) + +After successful creation of the app, a pop-up will appear displaying the app's client ID. Copy the client ID and secret, as you will need it to configure your Java client. + +![Create api key in console](/img/java-spring/api-create-clientid-secret.png) + +## Spring Setup + +Now that you have configured your web application on the ZITADEL side, you can proceed with the integration of your Spring client. +This guide will reference the [example repository](https://github.com/zitadel/zitadel-java) and explain the necessary steps taken in there. +If your starting from scratch, you can use the Spring Initializer with the [following setup](https://start.spring.io/#!type=maven-project&language=java&platformVersion=3.2.1&packaging=jar&jvmVersion=17&dependencies=web,lombok,oauth2-resource-server) as a base. + +### Support class + +To be able to take the most out of ZITADELs RBAC, we first need to create a CustomAuthorityOpaqueTokenIntrospector, that will +customize the introspection behaviour and map the role claims (`urn:zitadel:iam:org:project:roles`) +into Spring Security `authiorities`, which can be used later on to determine the granted permissions. + +So in your application, create a `support/zitadel` package and in there the `CustomAuthorityOpaqueTokenIntrospector.java`: + +```java reference +https://github.com/zitadel/zitadel-java/blob/main/api/src/main/java/demo/app/support/zitadel/CustomAuthorityOpaqueTokenIntrospector.java +``` + +### Application server configuration + +As we have now our support class, we can now create and configure the application server itself. + +In a new `config` package, create the `WebSecurityConfig.java`. +This class will take care of the authorization by require the calls on `/api/tasks` to be authorized. Any other endpoint will be public by default. +It will also use the just created CustomAuthorityOpaqueTokenIntrospector for the introspection call: + +```java reference +https://github.com/zitadel/zitadel-java/blob/main/api/src/main/java/demo/app/config/WebSecurityConfig.java +``` + +For the authorization (and the server in general) to work, the application needs some configuration, so please provide the following to your `application.yml` (resources folder): + +```yaml reference +https://github.com/zitadel/zitadel-java/blob/main/api/src/main/resources/application.yml +``` + +Note that the `introspection-uri`, `client-id` and `client-secret` are only placeholders. You can either change them in here using the values provided by ZITADEL +or pass them later on as arguments when starting the application. + +### Create example API + +Create a `api` package with a `ExampleController.java` file with the content below. This will create an API with three endpoints / methods: +- `/api/healthz`: can be called by anyone and always returns `OK` +- `/api/tasks (GET)`: requires authorization and returns the available tasks +- `/api/tasks (POST)`: requires authorization with granted `admin` role and adds the task to the list + +If authorization is required, the token must not be expired and the API has to be part of the audience (either client_id or project_id). + +For tests we will use a Personal Access Token or the [Java Spring web example](../login/java-spring). + +```java reference +https://github.com/zitadel/zitadel-java/blob/main/api/src/main/java/demo/app/api/ExampleController.java +``` + +## Test API + +In case you've created your own application and depending on your development setup you might need to build the application first: + +```bash +mvn clean package -DskipTests +``` + +You will need to provide the `introspection-uri` (your ZITADEL domain> /oauth/v2/introspect), the `client-id` and `client-secret` previously created: + +```bash +java \ + -Dspring.security.oauth2.resourceserver.opaquetoken.introspection-uri= \ + -Dspring.security.oauth2.resourceserver.opaquetoken.client-id= \ + -Dspring.security.oauth2.resourceserver.opaquetoken.client-secret= \ + -jar api/target/api-0.0.2-SNAPSHOT.jar +``` + +This could look like: + +```bash +java \ + -Dspring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://my-domain.zitadel.cloud/oauth/v2/introspect \ + -Dspring.security.oauth2.resourceserver.opaquetoken.client-id=243861220627644836@example \ + -Dspring.security.oauth2.resourceserver.opaquetoken.client-secret=WJKLF3kfPOi3optkg9vi3jmfjv8oj32nfiäohj!FSC09RWUSR \ + -jar web/target/web-0.0.2-SNAPSHOT.jar +``` + +### Public endpoint + +Now you can call the API by browser or curl. Try the healthz endpoint first: + +```bash +curl -i http://localhost:18090/api/healthz +``` + +it should return something like: + +``` +HTTP/1.1 200 +Vary: Origin +Vary: Access-Control-Request-Method +Vary: Access-Control-Request-Headers +X-Content-Type-Options: nosniff +X-XSS-Protection: 0 +Cache-Control: no-cache, no-store, max-age=0, must-revalidate +Pragma: no-cache +Expires: 0 +X-Frame-Options: DENY +Content-Type: text/plain;charset=UTF-8 +Content-Length: 2 +Date: Mon, 15 Jan 2024 09:07:21 GMT + +OK +``` + +### Task list + +and the task list endpoint: + +```bash +curl -i http://localhost:18090/api/tasks +``` + +it will return: + +``` +HTTP/1.1 401 +Vary: Origin +Vary: Access-Control-Request-Method +Vary: Access-Control-Request-Headers +WWW-Authenticate: Bearer +X-Content-Type-Options: nosniff +X-XSS-Protection: 0 +Cache-Control: no-cache, no-store, max-age=0, must-revalidate +Pragma: no-cache +Expires: 0 +X-Frame-Options: DENY +Content-Length: 0 +Date: Mon, 15 Jan 2024 09:07:55 GMT +``` + +Get a valid access_token for the API. You can either achieve this by getting an access token with the project_id in the audience +(e.g. by using the [Spring Boot web example](../login/java-spring)) use a PAT of a service account. + +If you provide a valid Bearer Token: + +```bash +curl -i -H "Authorization: Bearer ${token}" http://localhost:18090/api/tasks +``` + +it will return an empty list: +``` +HTTP/1.1 200 +Vary: Origin +Vary: Access-Control-Request-Method +Vary: Access-Control-Request-Headers +X-Content-Type-Options: nosniff +X-XSS-Protection: 0 +Cache-Control: no-cache, no-store, max-age=0, must-revalidate +Pragma: no-cache +Expires: 0 +X-Frame-Options: DENY +Content-Type: application/json +Transfer-Encoding: chunked +Date: Mon, 15 Jan 2024 09:15:10 GMT + +[] +``` + +### Try to add a new task + +Let's see what happens if you call the tasks endpoint: + +```bash + curl -i -X POST -H "Authorization: Bearer ${token}" -H "Content-Type: application/json" --data 'my new task' http://localhost:18090/api/tasks +``` + +it will complain with a permission denied (missing `admin` role): +``` +HTTP/1.1 403 +Vary: Origin +Vary: Access-Control-Request-Method +Vary: Access-Control-Request-Headers +WWW-Authenticate: Bearer error="insufficient_scope", error_description="The request requires higher privileges than provided by the access token.", error_uri="https://tools.ietf.org/html/rfc6750#section-3.1" +X-Content-Type-Options: nosniff +X-XSS-Protection: 0 +Cache-Control: no-cache, no-store, max-age=0, must-revalidate +Pragma: no-cache +Expires: 0 +X-Frame-Options: DENY +Content-Length: 0 +Date: Mon, 15 Jan 2024 09:24:39 GMT +``` + +### Add admin role + +So let's create the role and grant it to the user. To do so, go to your project in ZITADEL Console +and create the role by selecting `Roles` in the navigation and then clicking on the `New Role` button. +Finally, create the role as shown below: + +![Create project role in console](/img/java-spring/api-project-role.png) + +After you have created the role, let's grant it the user, who requested the tasks. +Click on `Authorization` in the navigation and create a new one by selecting the user and the `admin` role. +After successful creation, it should look like: + +![Created authorization in console](/img/java-spring/api-project-auth.png) + +So you should now be able to add a new task: + +```bash +curl -i -X POST -H "Authorization: Bearer ${token}" -H "Content-Type: application/json" --data 'my new task' http://localhost:18090/api/tasks +``` + +which will report back the successful addition: +``` +HTTP/1.1 200 +Vary: Origin +Vary: Access-Control-Request-Method +Vary: Access-Control-Request-Headers +X-Content-Type-Options: nosniff +X-XSS-Protection: 0 +Cache-Control: no-cache, no-store, max-age=0, must-revalidate +Pragma: no-cache +Expires: 0 +X-Frame-Options: DENY +Content-Type: application/json +Content-Length: 10 +Date: Mon, 15 Jan 2024 09:26:11 GMT + +task added +``` + +Let's now retrieve the task list again: + +```bash +curl -i -H "Authorization: Bearer ${token}" http://localhost:18090/api/tasks +``` + +As you can see your new task is listed: +``` +HTTP/1.1 200 +Vary: Origin +Vary: Access-Control-Request-Method +Vary: Access-Control-Request-Headers +X-Content-Type-Options: nosniff +X-XSS-Protection: 0 +Cache-Control: no-cache, no-store, max-age=0, must-revalidate +Pragma: no-cache +Expires: 0 +X-Frame-Options: DENY +Content-Type: application/json +Transfer-Encoding: chunked +Date: Mon, 15 Jan 2024 09:26:48 GMT + +["my new task"] +``` diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index aedb22e6ca..da5a601136 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -178,7 +178,7 @@ module.exports = { selector: "div#", }, prism: { - additionalLanguages: ["csharp", "dart", "groovy", "regex"], + additionalLanguages: ["csharp", "dart", "groovy", "regex", "java", "php"], }, colorMode: { defaultMode: "dark", diff --git a/docs/sidebars.js b/docs/sidebars.js index fbca39eedc..276082eac4 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -16,7 +16,8 @@ module.exports = { "examples/login/flutter", "examples/login/nextjs", "examples/login/go", - "examples/login/symfony" + "examples/login/symfony", + "examples/login/java-spring", ], collapsed: true, }, @@ -28,6 +29,7 @@ module.exports = { "examples/secure-api/python-flask", "examples/secure-api/dot-net", "examples/secure-api/nodejs-nestjs", + "examples/secure-api/java-spring", ], collapsed: true, }, diff --git a/docs/static/img/java-spring/api-create-auth.png b/docs/static/img/java-spring/api-create-auth.png new file mode 100644 index 0000000000..b5d8ee3814 Binary files /dev/null and b/docs/static/img/java-spring/api-create-auth.png differ diff --git a/docs/static/img/java-spring/api-create-clientid-secret.png b/docs/static/img/java-spring/api-create-clientid-secret.png new file mode 100644 index 0000000000..f0882b5a83 Binary files /dev/null and b/docs/static/img/java-spring/api-create-clientid-secret.png differ diff --git a/docs/static/img/java-spring/api-create.png b/docs/static/img/java-spring/api-create.png new file mode 100644 index 0000000000..1c21cf0706 Binary files /dev/null and b/docs/static/img/java-spring/api-create.png differ diff --git a/docs/static/img/java-spring/api-project-auth.png b/docs/static/img/java-spring/api-project-auth.png new file mode 100644 index 0000000000..741c08a558 Binary files /dev/null and b/docs/static/img/java-spring/api-project-auth.png differ diff --git a/docs/static/img/java-spring/api-project-role.png b/docs/static/img/java-spring/api-project-role.png new file mode 100644 index 0000000000..a819f6b8d3 Binary files /dev/null and b/docs/static/img/java-spring/api-project-role.png differ diff --git a/docs/static/img/java-spring/app-create-auth.png b/docs/static/img/java-spring/app-create-auth.png new file mode 100644 index 0000000000..e54b41bd49 Binary files /dev/null and b/docs/static/img/java-spring/app-create-auth.png differ diff --git a/docs/static/img/java-spring/app-create-clientid.png b/docs/static/img/java-spring/app-create-clientid.png new file mode 100644 index 0000000000..76473e47d6 Binary files /dev/null and b/docs/static/img/java-spring/app-create-clientid.png differ diff --git a/docs/static/img/java-spring/app-create-redirect.png b/docs/static/img/java-spring/app-create-redirect.png new file mode 100644 index 0000000000..cd1d7657fe Binary files /dev/null and b/docs/static/img/java-spring/app-create-redirect.png differ diff --git a/docs/static/img/java-spring/app-create.png b/docs/static/img/java-spring/app-create.png new file mode 100644 index 0000000000..ca78b1ec0c Binary files /dev/null and b/docs/static/img/java-spring/app-create.png differ diff --git a/docs/static/img/java-spring/app-profile.png b/docs/static/img/java-spring/app-profile.png new file mode 100644 index 0000000000..e20178168d Binary files /dev/null and b/docs/static/img/java-spring/app-profile.png differ