Skip to content

Commit 05183e0

Browse files
author
Santiago Gonzalez
committed
Udate README. Update error handling
1 parent 770ee46 commit 05183e0

File tree

4 files changed

+69
-51
lines changed

4 files changed

+69
-51
lines changed

README.md

+50-33
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ This sample demonstrates a Java web application signing-in a user with the Micro
2828
### Scenario
2929

3030
1. The Java web application uses the [Microsoft Authentication Library for Java (MSAL4J)](https://github.com/AzureAD/microsoft-authentication-library-for-java) to obtain an Access token from the Microsoft identity platform for the authenticated user.
31-
1. The access token is then used as a bearer token to authorize the caller in the Java web API and then subsequently exchanged for another access token for the Microsoft Graph API.
31+
1. The access token is then used as a bearer token to the request to the Java web API. The web API validates the access token using Spring Security, exchanges the incoming access token for a Microsoft Graph access token using OAuth2.0 On-behalf-of flow, and uses the new access token to request information from the Graph Me endpoint.
3232

3333
The flow is as follows:
3434

3535
1. Sign-in the user in the client(web) application.
3636
1. Acquire an access token for the Java Web API and call it.
37-
1. The Java Web API authorizes the caller and then calls another downstream Web API ([The Microsoft Graph](https://graph.microsoft.com)) after obtaining another [access token](https://docs.microsoft.com/azure/active-directory/develop/access-tokens) using the [on-behalf-of](https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow) flow.
37+
1. The Java Web API validates the access token using Spring Security and then calls another downstream Web API ([The Microsoft Graph](https://graph.microsoft.com)) after obtaining another [access token](https://docs.microsoft.com/azure/active-directory/develop/access-tokens) using the [on-behalf-of](https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow) flow.
3838

3939
## How to run this sample
4040

@@ -48,7 +48,10 @@ To run this sample, you'll need:
4848

4949
To successfully use this sample, you need a working installation of [Java](https://openjdk.java.net/install/) and [Maven](https://maven.apache.org/).
5050

51-
### Step 2: Clone or download this repository
51+
### Step 2: Clone or download this repository your account is present in more than one Azure AD tenant, select your profile at the top right corner in the menu on top of the page, and then **switch directory**.
52+
Change your portal session to the desired Azure AD tenant.
53+
1. In the portal menu, select the **Azure Active Directory** service, and then select **App registrations**.
54+
5255

5356
From your shell or command line:
5457

@@ -65,10 +68,8 @@ There are two projects in this sample. Each needs to be registered separately in
6568
As a first step you'll need to:
6669

6770
1. Sign in to the [Azure portal](https://portal.azure.com) using either a work or school account or a personal Microsoft account.
68-
1. If your account is present in more than one Azure AD tenant, select your profile at the top right corner in the menu on top of the page, and then **switch directory**.
69-
Change your portal session to the desired Azure AD tenant.
70-
1. In the portal menu, select the **Azure Active Directory** service, and then select **App registrations**.
71-
71+
1. If your account is present in more than one Azure AD tenant, select your profile at the top right corner in the menu on top of the page, and then switch directory. Change your portal session to the desired Azure AD tenant.
72+
In the portal menu, select the Azure Active Directory service, and then select App registrations.
7273
> In the next steps, you might need the tenant name (or directory name) or the tenant ID (or directory ID). These are presented in the **Properties** of the Azure Active Directory window respectively as *Name* and *Directory ID*
7374
7475
#### Register the Web Api app (Java-webapi)
@@ -116,12 +117,12 @@ Open `application.properties` in the src/main/resources folder. Fill in with you
116117
- *Enter_the_Application_Id_here* with the **Application (client) ID**.
117118
- *Enter_the_Client_Secret_Here* with the **key value** noted earlier.
118119

119-
#### Register the client web app (Java_webapp)
120+
#### Register the client web app (Java-webapp)
120121

121122
1. Navigate to the Microsoft identity platform for developers [App registrations](https://go.microsoft.com/fwlink/?linkid=2083908) page.
122123
1. Click **New registration**.
123124
1. In the **Register an application page** that appears, enter your application's registration information:
124-
- In the **Name** section, enter a meaningful application name that will be displayed to users of the app, for example `java_webapp`.
125+
- In the **Name** section, enter a meaningful application name that will be displayed to users of the app, for example `java-webapp`.
125126
- Change **Supported account types** to **Accounts in any organizational directory and personal Microsoft accounts (e.g. Skype, Xbox, Outlook.com)**.
126127
> Note that there are more than one redirect URIs used in this sample. You'll need to add them from the **Authentication** tab later after the app has been created successfully.
127128
1. Click on the **Register** button to create the application.
@@ -130,7 +131,6 @@ Open `application.properties` in the src/main/resources folder. Fill in with you
130131
- In the **Platform configurations** section select **Add a platform** and create a new **Web** application
131132
- Enter the following as the redirect URI: `http://localhost:8080/msal4jsample/secure/aad`
132133
- Click on **Configure** to save your changes.
133-
- Do the same for: `http://localhost:8080/msal4jsample/graph/me`
134134
- Click the **Save** button to save the the redirect URI changes.
135135
1. In the Application menu blade, click on the **Certificates & secrets** to open the page where we can generate secrets and upload certificates.
136136
1. In the **Client secrets** section, click on **New client secret**:
@@ -151,7 +151,7 @@ Open `application.properties` in the msal-web-sample/src/main/resources folder.
151151

152152
- Replace *Enter_the_Application_Id_here* with the **Application (client) ID**.
153153
- Replace *Enter_the_Client_Secret_Here* with the **key value** noted earlier.
154-
- Replace *OboApi* with the API exposed in the `Web Api app` **(api://{clientId})**.
154+
- Replace *Enter_the_Api_Scope_Here* with the API exposed in the `Web Api app` **(api://{clientId})**.
155155

156156
#### HTTPS on localhost
157157

@@ -322,25 +322,12 @@ There are many key points in this sample to make the On-Behalf-Of-(OBO) flow wor
322322
323323
1. **ApiController** class
324324
325-
Contains the api(graphMeApi) to trigger the obo flow. The graphMeApi method gets the obo access token using **MsalAuthHelper**. The `callMicrosoftGraphEndPoint` method calls the Microsoft graph API using obo token.
326-
327-
```Java
328-
String oboAccessToken = msalAuthHelper.getOboToken("https://graph.microsoft.com/.default");
329-
330-
return callMicrosoftGraphMeEndpoint(oboAccessToken);
331-
```
332-
333-
Important things to notice:
325+
Uses the [Java Microsoft Graph SDK](https://github.com/microsoftgraph/msgraph-sdk-java) to call the the api(graphMeApi). The `GraphServiceClient` uses the `oboAuthProvider` to acquire the necessary tokens to access the Graph Me endpoint.
334326
335-
- The **scope** [.default](https://docs.microsoft.com/azure/active-directory/developv2-permissions-and-consent#the-default-scope) is a built-in scope for every application that refers to the static list of permissions configured on the application registration. In our scenario here, it enables the user to grant consent for permissions for both the Web API and the downstream API (Microsoft Graph). For example, the permissions for the Web API and the downstream API (Microsoft Graph) are listed below:
336-
- Web Api sample (access_as_user)
337-
- Microsoft Graph (user.read)
338-
339-
- When you use the `.default` scope, the end user is prompted for a combined set of permissions that include scopes from both the **Web Api** and **Microsoft Graph**.
340327
341328
2. **SecurityResourceServerConfig** class
342329
343-
Token Validation of the caller happens in this class, where the access token presented by the client app is validated using Spring Security and another access token is obtained using the on-behalf-of flow
330+
Token Validation of the caller happens in this class, where the access token presented by the client app is validated using Spring Security.
344331
345332
```Java
346333
http
@@ -351,20 +338,50 @@ There are many key points in this sample to make the On-Behalf-Of-(OBO) flow wor
351338
.access("#oauth2.hasScope('" + accessAsUserScope + "')"); // required scope to access /api URL
352339
```
353340
354-
3. **MsalAuthHelper** class
341+
3. **OboAuthProvider** class
355342
356-
Contains the methods to obtain the auth token and obo token to enable on-behalf-of flow.
343+
Contains the methods to exchange the incoming token for an access token for Microsoft Graph.
357344
358345
A code snippet showing how to obtain obo token
359346
360347
```Java
361-
OnBehalfOfParameters parameters =
362-
OnBehalfOfParameters.builder(Collections.singleton(scope),
363-
new UserAssertion(authToken))
348+
IAuthenticationResult authResult;
349+
ConfidentialClientApplication application = null;
350+
try {
351+
application = ConfidentialClientApplication
352+
.builder(clientId, ClientCredentialFactory.createFromSecret(secret))
353+
.authority(authority)
354+
.build();
355+
356+
String cachedTokens = cacheManager.getCache("tokens").get(cacheKey, String.class);
357+
if (cachedTokens != null) {
358+
application.tokenCache().deserialize(cachedTokens);
359+
}
360+
361+
SilentParameters silentParameters =
362+
SilentParameters.builder(Collections.singleton(scope))
364363
.build();
365-
366-
auth = application.acquireToken(parameters).join();
364+
authResult = application.acquireTokenSilently(silentParameters).join();
365+
} catch (Exception ex) {
366+
if (ex.getCause() instanceof MsalException) {
367+
OnBehalfOfParameters parameters =
368+
OnBehalfOfParameters.builder(Collections.singleton(scope),
369+
new UserAssertion(authToken))
370+
.build();
371+
authResult = application.acquireToken(parameters).join();
372+
} else {
373+
throw new AuthException(String.format("Error acquiring token from AAD: %s", ex.getMessage()),
374+
ex.getCause());
375+
}
376+
}
367377
```
378+
Important things to notice:
379+
- `application.acquireTokenSilently` is attempted first to try and use the cached tokens. If the silent call fails, the sample falls back to trying to acquire a token via obo.
380+
- The **scope** [.default](https://docs.microsoft.com/azure/active-directory/developv2-permissions-and-consent#the-default-scope) is a built-in scope for every application that refers to the static list of permissions configured on the application registration. In our scenario here, it enables the user to grant consent for permissions for both the Web API and the downstream API (Microsoft Graph). For example, the permissions for the Web API and the downstream API (Microsoft Graph) are listed below:
381+
- Web Api sample (access_as_user)
382+
- Microsoft Graph (user.read)
383+
384+
- When you use the `.default` scope, the end user is prompted for a combined set of permissions that include scopes from both the **Web Api** and **Microsoft Graph**.
368385
369386
## Feedback, Community Help and Support
370387

msal-obo-sample/src/main/java/com/microsoft/azure/msalobosample/ApiController.java

+14-12
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33

44
package com.microsoft.azure.msalobosample;
55

6-
import com.fasterxml.jackson.core.JsonProcessingException;
76
import com.fasterxml.jackson.databind.ObjectMapper;
87
import com.microsoft.graph.models.User;
98
import com.microsoft.graph.requests.GraphServiceClient;
109
import okhttp3.Request;
1110
import org.springframework.beans.factory.annotation.Autowired;
11+
import org.springframework.http.ResponseEntity;
1212
import org.springframework.web.bind.annotation.RequestMapping;
1313
import org.springframework.web.bind.annotation.RestController;
1414

@@ -19,16 +19,18 @@ public class ApiController {
1919
OboAuthProvider oboAuthProvider;
2020

2121
@RequestMapping("/graphMeApi")
22-
public String graphMeApi() throws JsonProcessingException {
23-
24-
GraphServiceClient<Request> graphClient = GraphServiceClient
25-
.builder()
26-
.authenticationProvider(oboAuthProvider)
27-
.buildClient();
28-
29-
User user = graphClient.me().buildRequest().get();
30-
ObjectMapper objectMapper = new ObjectMapper();
31-
return objectMapper.writeValueAsString(user);
22+
public ResponseEntity<String> graphMeApi() {
23+
try {
24+
GraphServiceClient<Request> graphClient = GraphServiceClient
25+
.builder()
26+
.authenticationProvider(oboAuthProvider)
27+
.buildClient();
28+
29+
User user = graphClient.me().buildRequest().get();
30+
ObjectMapper objectMapper = new ObjectMapper();
31+
return ResponseEntity.status(200).body(objectMapper.writeValueAsString(user));
32+
} catch (Exception ex) {
33+
return ResponseEntity.status(500).body(String.format("%s: %s", ex.getCause(), ex.getMessage()));
34+
}
3235
}
33-
3436
}

msal-obo-sample/src/main/java/com/microsoft/azure/msalobosample/OboAuthProvider.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
import com.microsoft.aad.msal4j.OnBehalfOfParameters;
1212
import com.microsoft.aad.msal4j.SilentParameters;
1313
import com.microsoft.aad.msal4j.UserAssertion;
14-
import com.microsoft.graph.authentication.IAuthenticationProvider;
14+
import com.microsoft.graph.authentication.BaseAuthenticationProvider;
15+
import org.jetbrains.annotations.NotNull;
1516
import org.springframework.beans.factory.annotation.Autowired;
1617
import org.springframework.beans.factory.annotation.Value;
1718
import org.springframework.cache.CacheManager;
@@ -27,7 +28,7 @@
2728
import java.util.concurrent.CompletableFuture;
2829

2930
@Component
30-
class OboAuthProvider implements IAuthenticationProvider {
31+
class OboAuthProvider extends BaseAuthenticationProvider {
3132

3233
@Value("${security.oauth2.client.authority}")
3334
private String authority;
@@ -44,6 +45,7 @@ class OboAuthProvider implements IAuthenticationProvider {
4445
@Autowired
4546
CacheManager cacheManager;
4647

48+
@NotNull
4749
@Override
4850
public CompletableFuture<String> getAuthorizationTokenAsync(@Nonnull URL url) {
4951

@@ -90,8 +92,6 @@ public CompletableFuture<String> getAuthorizationTokenAsync(@Nonnull URL url) {
9092
* Retrieves the access token token included in the incoming request. This access token will
9193
* be exchanged for an access token to access Microsoft Graph, on behalf of the user that is
9294
* signed in the web application.
93-
*
94-
* @return
9595
*/
9696
private String getAuthToken() {
9797
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

msal-web-sample/src/main/resources/application.properties

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ aad.redirectUri=http://localhost:8080/msal4jsample/secure/aad
66
aad.logoutRedirectUri=http://localhost:8080
77

88
# AAD scopes
9-
aad.graphDefaultScope =https://graph.microsoft.com/.default
10-
aad.apiDefaultScope=api://a0f1b2d6-b573-44f4-8cb3-566933d2b71f/.default
9+
aad.apiDefaultScope=Enter_the_Api_Scope_Here
1110

1211
# Change the port to 8443 if running HTTPS. Also update port in the redirect/logoutRedirectUri above
1312
server.port=8080

0 commit comments

Comments
 (0)