Add Authentication and Authorization to the Application
- How to add authentication and authorization to the application
- How to test security aspects with the application running locally
- Step 1
In this tutorial you add authentication to your application by adding the
cds-starter-cloudfoundrydependency, which enables CAP Java’s secure-by-default behavior based on Spring Security.- Edit the
pom.xmlin thesrvdirectory (not thepom.xmlfile located in the root project folder) and under the<dependencies>tag add thecds-starter-cloudfoundrydependency. Make sure you Save the file.xmlCopy<dependency> <groupId>com.sap.cds</groupId> <artifactId>cds-starter-cloudfoundry</artifactId> </dependency>
What kind of HTTP authentication is used for testing the application locally?
- Edit the
- Step 2
You will now demonstrate this secure-by-default behavior using the
OrdersService. For now, you only require that the user who wants to create the order must be authenticated. CAP provides built-in users that represent common authentication scenarios for local development.-
Start the application with the command
mvn spring-boot:run. -
Open the
requests.httpfile and execute the first Create Order request by choosing Send Request above it. -
Observe that the response contains status
HTTP/1.1 401.
-
To create an order, you need to provide credentials. For local development CAP has built-in mock users. Modify the request like follows:
HTTPCopy### Create Order POST http://localhost:8080/odata/v4/OrdersService/Orders Content-Type: application/json Authorization: Basic authenticated: { "items": [ { "book_ID": "abed2f7a-c50e-4bc5-89fd-9a00a54b4b16", "amount": 2 } ] }Here you have added the
AuthorizationHTTP header to the request and provided the credentials for the built-inauthenticatedmock user. This user has an empty password. -
Execute the request again and see that the order is now created.
-
When choosing entities of the
OrdersServiceon the welcome page you will also have to provide credentials now. You can useauthenticateduser and empty password there as well.
-
- Step 3
Built-in mock users are good for initial local testing, but you may need separate different users for your application to test it further. You will now add some custom mock users to the application:
-
Add the
securitysection to theapplication.yamlfile undersrv/src/main/resourcesas follows:YAMLCopy--- spring: config.activate.on-profile: default sql.init.schema-locations: classpath:schema-h2.sql cds: datasource: auto-config.enabled: false security: mock: users: - name: klaus password: pass_klaus additional: firstName: Klaus lastName: Sussard email: Klaus.Sussard@mail.com - name: mia password: pass_mia additional: firstName: Mia lastName: Bonnellac email: Mia.Bonnellac@mail.comHere you defined two users that have no explicit role assignment and will implicitly belong to the
authenticated-userpseudo-role. -
Restart your application with the command
mvn spring-boot:run. In the startup logs you can observe the created mock users with their user names, roles, and passwords. They are added in addition to the built-in mock users.
-
Modify the HTTP request, you have used earlier to include credentials to one of the mock users:
HTTPCopy### Create Order POST http://localhost:8080/odata/v4/OrdersService/Orders Content-Type: application/json Authorization: Basic klaus:pass_klaus { "items": [ { "book_ID": "abed2f7a-c50e-4bc5-89fd-9a00a54b4b16", "amount": 2 } ] } -
Choose Send Request above it and see that a new order is created.
The payload of the response contains the name of your user in fields
createdByandmodifiedBy, provided by themanagedaspect that you added to your domain model earlier. -
- Step 4
You will now add a user role
Administratorsto your application.-
Add a new mock user to the
application.yamlfile after the existing users as follows:YAMLCopy--- spring: config.activate.on-profile: default sql.init.schema-locations: classpath:schema-h2.sql cds: datasource: auto-config.enabled: false security: mock: users: - name: klaus password: pass_klaus additional: firstName: Klaus lastName: Sussard email: Klaus.Sussard@mail.com - name: mia password: pass_mia additional: firstName: Mia lastName: Bonnellac email: Mia.Bonnellac@mail.com - name: sabine password: pass_sabine roles: - Administrators additional: firstName: Sabine lastName: Autumnpike email: Sabine.Autumnpike@mail.comYou used the attribute
rolesto add theAdministratorsrole to that user. -
Add the
annotatedefinition at the end of theservices.cdsfile in thesrvdirectory to make theAdminServiceavailable only to users with anAdministratorsrole:CDSCopyannotate AdminService @(requires: 'Administrators'); -
Restart your application with the command
mvn spring-boot:run. -
Add a new request to the
requests.httpfile:HTTPCopy### Read Products GET http://localhost:8080/odata/v4/AdminService/Products Accept: application/json Authorization: Basic sabine:pass_sabine -
Choose Send Request above this request and see that you receive the list of products.
-
Remove the
Authorizationheader or change the credentials to a different mock user. Observe that theAdminServiceis not available to them.
Which user has the administrator role?
-
- Step 5
CAP can do more than simple role-based authorizations. To illustrate that, you will implement the following use case:
- Each authenticated user should be able to view only their orders and order items.
- Administrator should be able to view all orders of all users.
You can use the
@restrictannotation to add more sophisticated authorization checks to your services.-
Modify the service definition for
OrdersServicein theservices.cdsfile in foldersrvas follows:CDSCopy// Define Orders Service service OrdersService { @(restrict: [ { grant: '*', to: 'Administrators' }, { grant: '*', where: 'createdBy = $user' } ]) entity Orders as projection on db.Orders; @(restrict: [ { grant: '*', to: 'Administrators' }, { grant: '*', where: 'parent.createdBy = $user' } ]) entity OrderItems as projection on db.OrderItems; }With that you grant administrators access to all orders, while regular users only see the orders that were created by them. As you expose
OrderItemsas a separate entity, you have to add security configuration there as well. We use a path expression across theparentassociation, which points toOrders, to limit the items to those belonging to orders that were created by the respective user. -
Restart your application with the command
mvn spring-boot:run. -
Execute an HTTP request to create orders with credentials of one of the mock users you added earlier:
HTTPCopy### Create Order as Mia POST http://localhost:8080/odata/v4/OrdersService/Orders Content-Type: application/json Authorization: Basic mia:pass_mia { "items": [ { "book_ID": "fd0c5fda-8811-4e20-bcff-3a776abc290a", "amount": 10 } ] }With separate requests you can see that each user, except administrators, only has access to their own orders and items. Execute the following requests by adding them to the
requests.httpfile to verify that:HTTPCopy### Read Orders as Mia GET http://localhost:8080/odata/v4/OrdersService/Orders?$expand=items Accept: application/json Authorization: Basic mia:pass_miaYou will see the own orders and items.
HTTPCopy### Read Orders as Klaus GET http://localhost:8080/odata/v4/OrdersService/OrderItems Accept: application/json Authorization: Basic klaus:pass_klausYou will not see any items.
HTTPCopy### Read Orders as Sabine (Administrator) GET http://localhost:8080/odata/v4/OrdersService/Orders?$expand=items Accept: application/json Authorization: Basic sabine:pass_sabineYou will see all orders and items.
Congratulations, you have learned how to add basic security to your application and test it locally. You can model your business users and implement authorization requirements.