Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[QUERY] Support needed setting up client credential flow with Spring Boot app #22642

Closed
2 tasks done
BillyBolton opened this issue Jun 29, 2021 · 14 comments · Fixed by #22717
Closed
2 tasks done

[QUERY] Support needed setting up client credential flow with Spring Boot app #22642

BillyBolton opened this issue Jun 29, 2021 · 14 comments · Fixed by #22717
Assignees
Labels
azure-spring All azure-spring related issues azure-spring-aad Spring active directory related issues. Client This issue points to a problem in the data-plane of the library. customer-reported Issues that are reported by GitHub users external to the Azure organization. question The issue doesn't require a change to the product in order to be resolved. Most issues start as that
Milestone

Comments

@BillyBolton
Copy link

Query/Question
Question also posted on StackOverflow here.

I'm trying to setup client credential flow with a Spring app to access a web api (both owned by myself). I've attempted to follow the Azure documentation Microsoft identity platform and the OAuth 2.0 client credentials flow and Quickstart: Configure a client application to access a web API but I'm running into a few problems because the documentation is not clear. Somewhere in my setup, Azure is forcing the user to sign-in, and then other error messages sprout from there. As we know, however, client credential should be machine to machine authorization so I'm not sure why this sign-in flow is happening.

Below are some photos of my setup from Azure and the applications.yml file. Any feedback would be helpful getting me up running.

application.yml

 azure:
  activedirectory:
    tenant-id: {my-web-app-tenant-id}
    client-id: {my-web-app-client-id}
    client-secret: {my-web-app-client-secret}
    authorization-clients:
     web-api:
       scopes:
         - api://example-api/Employees.Read.All
         - api://example-api/Employees.Write.All

Web-app and web-api registered applications

Web-api scopes and authorized client which matches web-app client

Web-app authentication setup

Web-App permissions, including permission for web-api

Why is this not a Bug or a feature Request?
A clear explanation of why is this not a bug or a feature request?

Setup (please complete the following information if applicable):

  • OS: Ubuntu 20.10
  • IDE: Visual Studio Code
  • Library/Libraries:
  • com.azure.spring:azure-spring-boot-starter-active-directory:3.5.0
  • org.springframework.boot:spring-boot-starter-oauth2-client

Information Checklist
Kindly make sure that you have added all the following information above and checkoff the required fields otherwise we will treat the issuer as an incomplete report

  • Query Added
  • Setup information Added
@ghost ghost added needs-triage Workflow: This is a new issue that needs to be triaged to the appropriate team. customer-reported Issues that are reported by GitHub users external to the Azure organization. question The issue doesn't require a change to the product in order to be resolved. Most issues start as that labels Jun 29, 2021
@joshfree joshfree added azure-spring All azure-spring related issues azure-spring-aad Spring active directory related issues. Client This issue points to a problem in the data-plane of the library. labels Jun 29, 2021
@ghost ghost removed the needs-triage Workflow: This is a new issue that needs to be triaged to the appropriate team. label Jun 29, 2021
@joshfree
Copy link
Member

Hi @BillyBolton, someone from the azure-spring team will follow up with you shortly.

/cc @stliu

@chenrujun chenrujun self-assigned this Jun 30, 2021
@chenrujun chenrujun added this to the [2021] August milestone Jun 30, 2021
@BillyBolton
Copy link
Author

Update:
From the steps here, I was able to get the token I wanted using Postman. The missing step was to make an App Role from the api, and then use application permissions for that role on the client side.

Unfortunately, I'm still getting the same sign-in flow though, which makes me think there's an issue on the Spring boot configuration. Would you be able to clarify what the applications.yml file needs to look like for this flow? (I've been looking at the documentation and it's really not clear).

 azure:
  activedirectory:
    tenant-id: {my-web-app-tenant-id}
    client-id: {my-web-app-client-id}
    client-secret: {my-web-app-client-secret}
    authorization-clients:
     web-api:
       scopes:
         - api://example-api/.default

@chenrujun
Copy link

Hi, @BillyBolton .

Thanks for reaching out.
Sorry for late response.

Could you please try this:

 azure:
  activedirectory:
    tenant-id: {my-web-app-tenant-id}
    client-id: {my-web-app-client-id}
    client-secret: {my-web-app-client-secret}
    authorization-clients:
     web-api:
       scopes:
         - api://example-api/.default
       authorization-grant-type: client_credentials   # Add this line.

And I'll create a PR soon about the docs and sample project.

@chenrujun chenrujun linked a pull request Jul 1, 2021 that will close this issue
@chenrujun
Copy link

Hi, @BillyBolton.

I tested it just now by this PR: #22717 .

azure-spring-boot-starter-active-directory supports client credential type.

Please make sure these steps are right:

  1. azure.activedirectory.authorization-clients.web-api.authorization-grant-type is set to client_credentials.
  2. In webapp, set webapi's permission is Application Permission. Refs: https://stackoverflow.com/questions/57379397/why-is-application-permissions-disabled-in-azure-ads-request-api-permissions
  3. If you set azure.activedirectory.authorization-clients.web-api.scopes=api://example-api/.default in webApp, then you should set azure.activedirectory.app-id-uri=api://example-api in webApi.

If you still have problem about client-credential type, please:

  1. Try to run my sample: In sample project, add content about client_credential. #22717 .
  2. Provide your sample, let me investigate your sample.

@BillyBolton
Copy link
Author

BillyBolton commented Jul 5, 2021

Hi @chenrujun, thanks for sending all of that over.

I realized I was using the wrong oauth dependency that was prompting the sign-in flow. This part is resolved.

Unfortunately, I'm getting a 401 response both in your sample and mine so I think there's a permission issue in my web-app configuration. For what it's worth, here is my sample. Do you know what I should be looking at closer for this?

I confirmed the steps you noted as well.
Capture

@chenrujun
Copy link

Hi, @BillyBolton.

Thanks for your response.
Sorry for making you confused.

Could you please try to add Directory.Read.All, and no admin consent is required.

image

I have a plan to delete Directory.Read.All from required scopes when azure.activedirectory.user-group.allowed-group-names is not configured.

Refs:

  1. [FEATURE REQ] Not require "Directory.Read.All" scope if "azure.activedirectory.user-group.allowed-group-names" not configured. #21284
  2. https://github.com/Azure/azure-sdk-for-java/blob/azure-spring-boot-starter-active-directory_3.6.1/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADWebAppConfiguration.java#L127

@BillyBolton
Copy link
Author

BillyBolton commented Jul 6, 2021

Hi @chenrujun, looks like I'm still getting a 401. I'm starting to wonder if this has something to do with my controller though. I've pushed an update to my sample based on how to preauthorize with application permissions. Can you take a look at this sample? Below are my manifest files for the web-app and web-api. Note: these are just dummy apps so credentials will be discarded after I can get this going.

To run my sample, from the app's top directory, in terminal type and run: ./setup.sh && ./gradlew client-credentials:bootRun

Let me know your thoughts once you can take a closer look.

API Manifest

{
	"id": "52818ddf-eecc-4023-9f42-fd6261b10da1",
	"acceptMappedClaims": null,
	"accessTokenAcceptedVersion": null,
	"addIns": [],
	"allowPublicClient": null,
	"appId": "f8f123d1-fd92-4037-b7b3-8ee9875aaaa2",
	"appRoles": [
		{
			"allowedMemberTypes": [
				"Application"
			],
			"description": "Application has the ability to make a delete request.",
			"displayName": "Delete",
			"id": "ed87223f-e0d5-4474-b532-75793fa47144",
			"isEnabled": true,
			"lang": null,
			"origin": "Application",
			"value": "Task.Delete"
		},
		{
			"allowedMemberTypes": [
				"Application"
			],
			"description": "Application has the ability to make a write request.",
			"displayName": "Write",
			"id": "24bb3e83-1994-4c56-bef6-4ec4f6e74c07",
			"isEnabled": true,
			"lang": null,
			"origin": "Application",
			"value": "Task.Write"
		},
		{
			"allowedMemberTypes": [
				"Application"
			],
			"description": "Application has the ability to make a read request.",
			"displayName": "Read",
			"id": "faa93a34-69a8-44c4-a3df-f4fdfe0710b1",
			"isEnabled": true,
			"lang": null,
			"origin": "Application",
			"value": "Task.Read"
		},
		{
			"allowedMemberTypes": [
				"Application"
			],
			"description": "Application has the ability to make an update request.",
			"displayName": "Update",
			"id": "5755a871-bfa1-4937-9f4a-2d44bdcdbd7e",
			"isEnabled": true,
			"lang": null,
			"origin": "Application",
			"value": "Task.Update"
		}
	],
	"oauth2AllowUrlPathMatching": false,
	"createdDateTime": "2021-06-23T14:43:24Z",
	"disabledByMicrosoftStatus": null,
	"groupMembershipClaims": null,
	"identifierUris": [
		"api://example-api"
	],
	"informationalUrls": {
		"termsOfService": null,
		"support": null,
		"privacy": null,
		"marketing": null
	},
	"keyCredentials": [],
	"knownClientApplications": [],
	"logoUrl": null,
	"logoutUrl": null,
	"name": "web-api",
	"oauth2AllowIdTokenImplicitFlow": false,
	"oauth2AllowImplicitFlow": false,
	"oauth2Permissions": [],
	"oauth2RequirePostResponse": false,
	"optionalClaims": null,
	"orgRestrictions": [],
	"parentalControlSettings": {
		"countriesBlockedForMinors": [],
		"legalAgeGroupRule": "Allow"
	},
	"passwordCredentials": [],
	"preAuthorizedApplications": [],
	"publisherDomain": "AadBolt.onmicrosoft.com",
	"replyUrlsWithType": [],
	"requiredResourceAccess": [
		{
			"resourceAppId": "00000003-0000-0000-c000-000000000000",
			"resourceAccess": [
				{
					"id": "e1fe6dd8-ba31-4d61-89e7-88639da4683d",
					"type": "Scope"
				}
			]
		}
	],
	"samlMetadataUrl": null,
	"signInUrl": null,
	"signInAudience": "AzureADMyOrg",
	"tags": [],
	"tokenEncryptionKeyId": null
}

Web-App Manifest

{
	"id": "f30f7535-41c1-4410-9fbb-10a82b27e632",
	"acceptMappedClaims": null,
	"accessTokenAcceptedVersion": null,
	"addIns": [],
	"allowPublicClient": null,
	"appId": "d0e54c13-eb2e-4e9b-8726-0f66d008d5a3",
	"appRoles": [],
	"oauth2AllowUrlPathMatching": false,
	"createdDateTime": "2021-06-23T15:40:13Z",
	"disabledByMicrosoftStatus": null,
	"groupMembershipClaims": null,
	"identifierUris": [],
	"informationalUrls": {
		"termsOfService": null,
		"support": null,
		"privacy": null,
		"marketing": null
	},
	"keyCredentials": [],
	"knownClientApplications": [],
	"logoUrl": null,
	"logoutUrl": null,
	"name": "web-app",
	"oauth2AllowIdTokenImplicitFlow": false,
	"oauth2AllowImplicitFlow": false,
	"oauth2Permissions": [],
	"oauth2RequirePostResponse": false,
	"optionalClaims": null,
	"orgRestrictions": [],
	"parentalControlSettings": {
		"countriesBlockedForMinors": [],
		"legalAgeGroupRule": "Allow"
	},
	"passwordCredentials": [
		{
			"customKeyIdentifier": null,
			"endDate": "2021-12-30T19:11:48.684Z",
			"keyId": "1dd89de9-418c-4eb3-b1c7-7fd3a9c058c1",
			"startDate": "2021-06-30T18:11:48.684Z",
			"value": null,
			"createdOn": "2021-06-30T18:11:48.9384558Z",
			"hint": "3~9",
			"displayName": "client-secret"
		}
	],
	"preAuthorizedApplications": [],
	"publisherDomain": "AadBolt.onmicrosoft.com",
	"replyUrlsWithType": [
		{
			"url": "http://localhost/auth-response",
			"type": "Web"
		}
	],
	"requiredResourceAccess": [
		{
			"resourceAppId": "00000003-0000-0000-c000-000000000000",
			"resourceAccess": [
				{
					"id": "e1fe6dd8-ba31-4d61-89e7-88639da4683d",
					"type": "Scope"
				},
				{
					"id": "06da0dbc-49e2-44d2-8312-53f166ab848a",
					"type": "Scope"
				}
			]
		},
		{
			"resourceAppId": "f8f123d1-fd92-4037-b7b3-8ee9875aaaa2",
			"resourceAccess": [
				{
					"id": "ed87223f-e0d5-4474-b532-75793fa47144",
					"type": "Role"
				},
				{
					"id": "24bb3e83-1994-4c56-bef6-4ec4f6e74c07",
					"type": "Role"
				},
				{
					"id": "faa93a34-69a8-44c4-a3df-f4fdfe0710b1",
					"type": "Role"
				},
				{
					"id": "5755a871-bfa1-4937-9f4a-2d44bdcdbd7e",
					"type": "Role"
				}
			]
		}
	],
	"samlMetadataUrl": null,
	"signInUrl": null,
	"signInAudience": "AzureADMyOrg",
	"tags": [],
	"tokenEncryptionKeyId": null
}

@chenrujun
Copy link

Hi, @BillyBolton ,

I created a PR:
https://github.com/BillyBolton/client-credential-sample/pull/1

You can add a break point at DefaultClientCredentialsTokenResponseClient#getTokenResponse, and debug it.

I get the request like this:
image

This request can not get access_token.

You said that:

From the steps here, I was able to get the token I wanted using Postman.

Could you please compare the request to your request in Postman?

@BillyBolton
Copy link
Author

Hey @chenrujun,

Sorry, I'm not clear where to find the DefaultClientCredentialsTokenResponseClient.java file, but this seems handy for troubleshooting. Can you walk through how to get there?

I merged your PR on my sample and I saw what you meant about not getting the access_token:

There was an unexpected error (type=Internal Server Error, status=500).
[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: 401 Unauthorized: [no body]
org.springframework.security.oauth2.client.ClientAuthorizationException: [invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: 401 Unauthorized: [no body]

This is strange because, as mentioned, I can get one from Postman. Here's how I generate that request:
Capture

This generates the following token:

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyIsImtpZCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyJ9.eyJhdWQiOiJhcGk6Ly9leGFtcGxlLWFwaSIsImlzcyI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0L2FjZmM5MmM4LTRlNzItNGFmOS1iZmY4LTYwZDYzMjA3OTM0MC8iLCJpYXQiOjE2MjU2NjIzMjIsIm5iZiI6MTYyNTY2MjMyMiwiZXhwIjoxNjI1NjY2MjIyLCJhaW8iOiJFMlpnWUlpeS9Ka3AxaWt4UWZoaHM0N1YwVTljQUE9PSIsImFwcGlkIjoiZDBlNTRjMTMtZWIyZS00ZTliLTg3MjYtMGY2NmQwMDhkNWEzIiwiYXBwaWRhY3IiOiIxIiwiaWRwIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvYWNmYzkyYzgtNGU3Mi00YWY5LWJmZjgtNjBkNjMyMDc5MzQwLyIsIm9pZCI6IjIzNzc2MjMzLTNjYWYtNDljZi05ZWQyLWI4MTNiZDlkZTdmOCIsInJoIjoiMC5BWDBBeUpMOHJISk8tVXFfLUdEV01nZVRRQk5NNWRBdTY1dE9oeVlQWnRBSTFhTjlBQUEuIiwicm9sZXMiOlsiVGFzay5SZWFkIiwiVGFzay5EZWxldGUiLCJUYXNrLlVwZGF0ZSIsIlRhc2suV3JpdGUiXSwic3ViIjoiMjM3NzYyMzMtM2NhZi00OWNmLTllZDItYjgxM2JkOWRlN2Y4IiwidGlkIjoiYWNmYzkyYzgtNGU3Mi00YWY5LWJmZjgtNjBkNjMyMDc5MzQwIiwidXRpIjoiMTA0Rk40OXNtVW03eFpjVmZidUZBQSIsInZlciI6IjEuMCJ9.FREjiG6HOxXocch6KEbhuQzh-Qs4sf1KT54ggvAubIhwVRPhe7Tmnr93B3w39PqAPSlQNtRPcD8XkWiNL7v0z3vGSBeUyR2oGvARBAMOqLlwI8nqF0Ddji4kQ6idAKFLy_scf8roAVcz5iQ_FFhZ37GQsEl2TaGbql-EkEkNIqO4tFpbxjpzj0rkbpqPnp2goU3nxU-_jhxGlqi102lQdF6J7qpMl84o-pr635nMfdPw7QQmi0FYWkVLdfHZ4IQQwp5SRHED3OMqsCOEJ2Of-Y4cpuv1kozbTvcP_-ohw4bsiLe3h_HJX5jNw3kWfgkFSpO7Mj-hpcLz3grNh9gUiw

And that token decodes to:

{
  "typ": "JWT",
  "alg": "RS256",
  "x5t": "nOo3ZDrODXEK1jKWhXslHR_KXEg",
  "kid": "nOo3ZDrODXEK1jKWhXslHR_KXEg"
}.{
  "aud": "api://example-api",
  "iss": "https://sts.windows.net/acfc92c8-4e72-4af9-bff8-60d632079340/",
  "iat": 1625662322,
  "nbf": 1625662322,
  "exp": 1625666222,
  "aio": "E2ZgYIiy/Jkp1ikxQfhhs47V0U9cAA==",
  "appid": "d0e54c13-eb2e-4e9b-8726-0f66d008d5a3",
  "appidacr": "1",
  "idp": "https://sts.windows.net/acfc92c8-4e72-4af9-bff8-60d632079340/",
  "oid": "23776233-3caf-49cf-9ed2-b813bd9de7f8",
  "rh": "0.AX0AyJL8rHJO-Uq_-GDWMgeTQBNM5dAu65tOhyYPZtAI1aN9AAA.",
  "roles": [
    "Task.Read",
    "Task.Delete",
    "Task.Update",
    "Task.Write"
  ],
  "sub": "23776233-3caf-49cf-9ed2-b813bd9de7f8",
  "tid": "acfc92c8-4e72-4af9-bff8-60d632079340",
  "uti": "104FN49smUm7xZcVfbuFAA",
  "ver": "1.0"
}.[Signature]

As we can see, the aud and roles line up with what I have setup in Azure. I'm not sure what the difference is though.

@chenrujun
Copy link

Hi, @BillyBolton ,

I reproduced your problem with your configuration.

Now I can run your project with my application.yml:
image

Here is the accessToken:
image

Here is my resource-server's configuration:
image

Here is my web-application's configuration:
image

So there must be something wrong with your application.yml
Now I have no idea about what is wrong with your app-registration.
Could you please create web-application and resource-server from zero?

@BillyBolton
Copy link
Author

BillyBolton commented Jul 8, 2021

Hey there @chenrujun,

Great news, I'm no longer getting the same error after re-creating the client-app and resource-server as per your recommendation. Thanks for pointing this out, although I'm not sure what the problem is. Happy to see this change though. :)

As you can see, the token contains the app role I've configured. I'm able to access the page (as opposed to before), however when I @PreAuthorize with @PreAuthorize("hasAuthority('APPROLE_Task.Delete')") or @PreAuthorize("hasRole('APPROLE_Task.Delete')"), I get access denied. I think this is the last point I'm not clear on in the documentation.

I've updated my sample if you want to take a look for more clarity. Note: the api endpoint I use sometimes needs to be woken up and might pre-maturely close the connection on the first call. Just try again and it should be okay. FYI.

TaskDelete

jwt

@chenrujun
Copy link

@chenrujun
Copy link

Hi, @BillyBolton ,
Has your problem been solved?
You can close this issue if your problem has been solved.

@BillyBolton
Copy link
Author

Hey @chenrujun,

I was going to investigate one more thing but I think I will open a separate issue if that arises. Indeed, my initial problem has been solved.

My team and I agree that the documentation for Azure Authentication with Spring apps is difficult to navigate, but I appreciate your quick responses helping me through. I wish I had reached out sooner! (I've been looking at this for longer than I care to admit.)

Thank you again for all your help. You were fantastic!

azure-sdk pushed a commit to azure-sdk/azure-sdk-for-java that referenced this issue Feb 17, 2023
Update roleManagementAlertOperations properties (Azure#22642)

* Start updating paths

* update all paths and examples

* Change patch operations to 204 instead of 200

* Update operations paths

* Add new properties to operation

* Update RoleManagementAlerts.json

* Delete GetAlertOperations.json

* Update GetAlertOperationById.json

* Fix accidental comma deletion

* Update GetAlertOperationById.json

* Update RefreshAlert.json

* Update RefreshAllAlerts.json

* Update RefreshAlert.json

* Fix date formatting

* Fix date format

* Fix date format

* Update RefreshAlert.json

* Remove unused type

* Add remediatable to custom words
@github-actions github-actions bot locked and limited conversation to collaborators Apr 11, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
azure-spring All azure-spring related issues azure-spring-aad Spring active directory related issues. Client This issue points to a problem in the data-plane of the library. customer-reported Issues that are reported by GitHub users external to the Azure organization. question The issue doesn't require a change to the product in order to be resolved. Most issues start as that
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants