AzureAD / microsoft-authentication-library-for-java

Microsoft Authentication Library (MSAL) for Java http://aka.ms/aadv2
MIT License
285 stars 143 forks source link

Trouble getting an access token using OnBehalfOfParameters #38

Closed afrancoc2000 closed 5 years ago

afrancoc2000 commented 5 years ago

Hi,

Do you have an example for validating the idToken?

I have a token from the Authentication header like this: "Bearer XXXXXXXXX" and I would like to create a filter like the AADAuthenticationFilter from spring boot using the msal library but I'm not sure how can I validate the token from the client ID.

What I've seen is that I need to get the access token from my id token but how can I do that using the ConfidentialClientApplication class?

After that I can create my principal using the token information like in the samples but how can I validate my token?

Thanks!

afrancoc2000 commented 5 years ago

So this is what I have:

ConfidentialClientApplication clientApplication = ConfidentialClientApplication.builder(
        clientId, ClientCredentialFactory.create(clientSecret))
            .authority(authority).build();

Set<String> scopes = new HashSet<>(Arrays.asList(scope.split(" ")));
UserAssertion assertion = new UserAssertion(idToken);

OnBehalfOfParameters params = OnBehalfOfParameters.builder(scopes, assertion).build();
CompletableFuture<AuthenticationResult> future = clientApplication.acquireToken(params);

AuthenticationResult result = future.get();

But I'm getting a Casting error:

java.lang.ClassCastException: java.util.Collections$SingletonList cannot be cast to java.lang.String
    at com.nimbusds.oauth2.sdk.util.URLUtils.serializeParameters(URLUtils.java:103) ~[oauth2-oidc-sdk-5.64.4.jar:5.64.4]
    at com.microsoft.aad.msal4j.TokenRequest.toOauthHttpRequest(TokenRequest.java:240) ~[msal4j-0.2.0-preview.jar:0.2.0-preview]
    at com.microsoft.aad.msal4j.TokenRequest.executeOauthRequestAndProcessResponse(TokenRequest.java:88) ~[msal4j-0.2.0-preview.jar:0.2.0-preview]
    at com.microsoft.aad.msal4j.ClientApplicationBase.acquireTokenCommon(ClientApplicationBase.java:249) ~[msal4j-0.2.0-preview.jar:0.2.0-preview]
    at com.microsoft.aad.msal4j.AcquireTokenByAuthorizationGrantSupplier.execute(AcquireTokenByAuthorizationGrantSupplier.java:71) ~[msal4j-0.2.0-preview.jar:0.2.0-preview]
    at com.microsoft.aad.msal4j.AuthenticationResultSupplier.get(AuthenticationResultSupplier.java:77) [msal4j-0.2.0-preview.jar:0.2.0-preview]
    at com.microsoft.aad.msal4j.AuthenticationResultSupplier.get(AuthenticationResultSupplier.java:37) [msal4j-0.2.0-preview.jar:0.2.0-preview]
    at java.util.concurrent.CompletableFuture$AsyncSupply.run$$$capture(CompletableFuture.java:1590) [?:1.8.0_202]
    at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java) [?:1.8.0_202]
    at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1582) [?:1.8.0_202]
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289) [?:1.8.0_202]
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056) [?:1.8.0_202]
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692) [?:1.8.0_202]
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157) [?:1.8.0_202]
java.util.concurrent.ExecutionException: java.lang.ClassCastException: java.util.Collections$SingletonList cannot be cast to java.lang.String
    at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
    at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895)
    at co.com.accessmanagement.web.security.MSALAuthenticationFilter.doFilterInternal(MSALAuthenticationFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:74)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:200)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:834)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1415)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.ClassCastException: java.util.Collections$SingletonList cannot be cast to java.lang.String
    at com.nimbusds.oauth2.sdk.util.URLUtils.serializeParameters(URLUtils.java:103)
    at com.microsoft.aad.msal4j.TokenRequest.toOauthHttpRequest(TokenRequest.java:240)
    at com.microsoft.aad.msal4j.TokenRequest.executeOauthRequestAndProcessResponse(TokenRequest.java:88)
    at com.microsoft.aad.msal4j.ClientApplicationBase.acquireTokenCommon(ClientApplicationBase.java:249)
    at com.microsoft.aad.msal4j.AcquireTokenByAuthorizationGrantSupplier.execute(AcquireTokenByAuthorizationGrantSupplier.java:71)
    at com.microsoft.aad.msal4j.AuthenticationResultSupplier.get(AuthenticationResultSupplier.java:77)
    at com.microsoft.aad.msal4j.AuthenticationResultSupplier.get(AuthenticationResultSupplier.java:37)
    at java.util.concurrent.CompletableFuture$AsyncSupply.run$$$capture(CompletableFuture.java:1590)
    at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java)
    at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1582)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)

What am I doing wrong?

afrancoc2000 commented 5 years ago

URLUtils.serializeParameters(URLUtils.java:103) is expecting a Map<String, String[]> and its getting a Map<String, List> thats the problem

sangonzal commented 5 years ago

@afrancoc2000 are you using oauth2-oidc-sdk anywhere else in your project? It looks like you are loading oauth2-oidc-sdk-5.64.4.jar:5.64.4, but you'll need to bump this up to at least 6.0 to be compatible with msal4j. Starting with 6.0, they introduced a breaking change to support multi-valued parameters. (msal4j only works with oauth2-oidc-sdk version > 6.0)

sangonzal commented 5 years ago

@afrancoc2000 to answer your first question, we don't have any examples for validating a token, as msal4j is about acquiring tokens for a protected resources and not protecting those resources or validating tokens. Unfortunately, we don't have a Java middleware library to perform the latter tasks at the moment. If you search online, you'll find some third party libraries that will help out with this.

afrancoc2000 commented 5 years ago

@sangonzal the code I posted above is a good example on how to do authentication using the Bearer Token, you were right, the problem was the oauth2-oidc-sdk library I removed it and the flow started working.

I can add a class to the examples folder if you'd like.

I'm still having trouble with the flow though, when I generate a token using https://login.microsoftonline.com/common/oauth2/v2.0/token with grant type client_credentials I get a "AADSTS50013: Assertion failed signature validation. [Reason - The provided signature value did not match the expected signature value." error.

And when I do the same using a Bearer Token from the frontend I get a "AADSTS50034: The user account Microsoft.AzureAD.Telemetry.Diagnostics.PII does not exist in the 9188040d-6c67-4c5b-b112-36a304b66dad directory. To sign into this application, the account must be added to the directory." error.

I have read that the AADSTS50034 is because I'm using guest users, but I need them and in the front they can authenticate so still no idea.

Do you know what my problem could be?

Thanks

sangonzal commented 5 years ago

@afrancoc2000 could you elaborate on what your scenario looks like ? Can you share a code snippet and/or a fiddler trace (instructions on how to capture) that would help with debugging?

afrancoc2000 commented 5 years ago

@sangonzal sure this is what I'm doing:

  1. first I have a frontend app in angular 5 that uses msal for javascript and I'm using the wrapper for angular to intercept the calls and get authenticated in the front using this.

  2. I have a backend app in spring boot using spring security where I have some API methods. In the app I'm adding a filter to spring security adding the "on behalf flow" to authenticate using the msal library for compatibility between the front and back.

  3. When the frontend needs a resource from the back it sends a request to the back using an header like this: Authentication=Bearer XXXXXXXXX this header is added by the msal javascript library when I authenticate.

The idea is to build a stateless authentication using msal on the backend and create the principal get roles and stuff from the access token.

This is the code:

SecurityAdapter

@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityAdapter extends WebSecurityConfigurerAdapter {

    @Autowired
    private MSALAuthenticationFilter aadAuthFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
           SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);

        http.authorizeRequests()
            .antMatchers("/api/**").authenticated()
            .anyRequest().permitAll();

        http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());

        http.logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .logoutSuccessUrl("/")
                .deleteCookies("JSESSIONID")
                .invalidateHttpSession(true);

        http.addFilterBefore(aadAuthFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

MSALAuthenticationFilter

public class MSALAuthenticationFilter extends OncePerRequestFilter {
    private static final Logger log = LoggerFactory.getLogger(MSALAuthenticationFilter.class);

    private static final String TOKEN_HEADER = "Authorization";
    private static final String TOKEN_TYPE = "Bearer ";

    // Properties from the application.properties file like clientId, tenant and stuff.

    private static final String CURRENT_USER_PRINCIPAL = "CURRENT_USER_PRINCIPAL";
    private static final String CURRENT_USER_ACCESS_TOKEN = "CURRENT_USER_ACCESS_TOKEN";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {

        final String authHeader = request.getHeader(TOKEN_HEADER);
        UserPrincipal principal = (UserPrincipal) request.getSession().getAttribute(CURRENT_USER_PRINCIPAL);

        if (authHeader != null && authHeader.startsWith(TOKEN_TYPE)) {
            try {
                final String idToken = authHeader.replace(TOKEN_TYPE, "");

                ConfidentialClientApplication clientApplication = ConfidentialClientApplication.builder(
                        clientId,
                        ClientCredentialFactory.create(clientSecret))
                        .authority(authority)
                        .build();

                Set<String> scopes = new HashSet<>(Arrays.asList(scope.split(" ")));
                UserAssertion assertion = new UserAssertion(idToken);

                OnBehalfOfParameters params = OnBehalfOfParameters.builder(scopes, assertion).build();
                CompletableFuture<AuthenticationResult> future = clientApplication.acquireToken(params);

                AuthenticationResult accessToken = future.get();

                if (principal == null) {
                    principal = principalManager.buildUserPrincipal(idToken, accessToken);

                    request.getSession().setAttribute(CURRENT_USER_PRINCIPAL, principal);
                    request.getSession().setAttribute(CURRENT_USER_ACCESS_TOKEN, accessToken);
                }
                final Authentication authentication = new PreAuthenticatedAuthenticationToken(
                        principal,
                        null,
                        convertGroupsToGrantedAuthorities(principal.getUserGroups()));

                authentication.setAuthenticated(true);
                log.info("Request token verification success. {}", authentication);
                SecurityContextHolder.getContext().setAuthentication(authentication);

            }
            catch (MalformedURLException | InterruptedException | ExecutionException ex) {
                log.error("Failed to authenticate", ex);
                throw new ServletException(ex);
            }

        }    
        filterChain.doFilter(request, response);
    }    
}

I'm still working on the principal and the stateless parts but that is the general idea.

afrancoc2000 commented 5 years ago

I don't think the problem is in the java code though, I think its the microsoft identity server because I tested the tokens using postman directly using this and I'm getting the same errors.

I have two tests the first using the token in the Authentication header, the one like Bearer XXXXXXX that one gives me a AADSTS50034 error.

The second one I'm generating an identity token for an external app using this and it gives me an AADSTS50013 error.

I'm a little lost in here I think this should work as I can authenticate in the front and I could authenticate before from java using the oauth flow but the backend is really a web API and needs to be authenticated with the Bearer token instead, so I can't use that aproach.

sangonzal commented 5 years ago

@afrancoc2000 So if I'm understanding correctly, user logs-ins to the front end app, which then calls the web API. You'd like to:

1) Make sure that only authenticated users can access the API 2) Assert some claims about the user making calls to the API (roles, etc)

In this case, msal4j is not the right library to be used here for validation of tokens. msal4j is used for acquiring tokens for protected resources (as you are doing correctly in the front end, when you are acquiring token to access your API), but not actually protecting resources. Instead, here you would want to either validate the tokens with another third party library or do it manually. Here is some information on how to do so

Lets say that now on top of protecting your API, you have to call another API (say for instance Microsoft Graph) from your your API (instead of the front end app), on behalf of the user. This is where msal4j, and more specifically the on-behalf-of flow, would come in handy. You would use the first bearer token, create an user assertion, and exchange it for a second access token, then used to get access the second web API (Microsoft Graph in this instance) (This is essentially what you are doing in you code sample). More info about OBO flow here

Please let me know if I'm misunderstanding your scenario.

sangonzal commented 5 years ago

If OBO is what you are trying to do, make sure that your web API is registered correctly. Here is an OBO sample written in .NET, but the app registration instructions should be the same for what you are trying to do.

What is the value that you are using for authority in your code?

afrancoc2000 commented 5 years ago

@sangonzal You're right what I really want is to validate the token and get the role authorities I'm going to see the code you're giving me.

I did create almost all the example of the "On behalf of" flow" so I can still create a pull request with an example if you'd like.

For the authority I'm using is https://login.microsoftonline.com/{mytenant}

Thanks!

afrancoc2000 commented 5 years ago

@sangonzal going further with my example, now after I validated the token that was given to me, I need to go to the graph API to get the user groups.

I'm using as authority "https://login.microsoftonline.com/{mytenant}" and as scope "openid user.read" and I'm getting this error:

AADSTS50034: The user account XXXXX does not exist in the {mytenant} directory. To sign into this application, the account must be added to the directory.

Do I have the properties wrong?

Thanks!

sangonzal commented 5 years ago

@afrancoc2000 Thanks, I am currently working on updating the web sample, which will have OBO.

Regarding the error you are running into, is the account that you are using registered in {mytenant}? Is the application also registered in {mytenant}? Is this happening for all users or just a subset?

afrancoc2000 commented 5 years ago

@sangonzal yes both the app and the user are registered in the tenant, the user has been already authenticated in the frontend app with msal for javascript and the token has been validated.

It happens to all users.

I checked and I have User.Read permission for the https://graph.microsoft.com/ API in Azure Active Directory > App Registrations > MyApp > Api Permissions.

sangonzal commented 5 years ago

@afrancoc2000 hmm as far as I'm aware, this should work. It's hard to say what could be happening without debugging.

Can you share any logs and a network trace? Your logs should have a CorrelationId- this would be helpful as well.

afrancoc2000 commented 5 years ago

sure,

I'm modifying this class from spring boot AzureADGraphClient.java to use the msal library.

This is the code I'm changing:

public AuthenticationResult acquireTokenForGraphApi(String idToken, String tenantId)
        throws MalformedURLException, ServiceUnavailableException, InterruptedException, ExecutionException {

    ConfidentialClientApplication clientApplication = ConfidentialClientApplication.builder(
            clientId,
            ClientCredentialFactory.create(clientSecret))
            .authority(aadAuthenticationProperties.getAuthority())
            .build();

    Set<String> scopes = new HashSet<>(Arrays.asList(aadAuthenticationProperties.getScope().split(" ")));
    UserAssertion assertion = new UserAssertion(idToken);

    OnBehalfOfParameters params = OnBehalfOfParameters.builder(scopes, assertion).build();
    CompletableFuture<AuthenticationResult> future = clientApplication.acquireToken(params);

    AuthenticationResult result = future.get();

    if (result == null) {
        throw new ServiceUnavailableException("unable to acquire on-behalf-of token for client " + clientId);
    }
    return result;
}

And this is the error I'm getting:

2019-05-20 20:15:23,298 [ERROR] com.microsoft.aad.msal4j.ConfidentialClientApplication [Correlation ID: a13ed17d-fed2-42e4-8fbd-96853ce1f3be] Execution of class com.microsoft.aad.msal4j.AcquireTokenByAuthorizationGrantSupplier failed.
com.microsoft.aad.msal4j.AuthenticationException: {"error_description":"AADSTS50034: The user account 0006BFFD847C5DF5 does not exist in the 8cca582e-0d23-4e13-bf43-30896ccafd4f directory. To sign into this application, the account must be added to the directory.\r\nTrace ID: 194c3a64-86d4-4894-9d7d-10e4a9dc0500\r\nCorrelation ID: a13ed17d-fed2-42e4-8fbd-96853ce1f3be\r\nTimestamp: 2019-05-21 01:15:22Z","error":"invalid_grant"}
    at com.microsoft.aad.msal4j.TokenRequest.executeOauthRequestAndProcessResponse(TokenRequest.java:169) ~[msal4j-0.2.0-preview.jar:0.2.0-preview]
    at com.microsoft.aad.msal4j.ClientApplicationBase.acquireTokenCommon(ClientApplicationBase.java:249) ~[msal4j-0.2.0-preview.jar:0.2.0-preview]
    at com.microsoft.aad.msal4j.AcquireTokenByAuthorizationGrantSupplier.execute(AcquireTokenByAuthorizationGrantSupplier.java:71) ~[msal4j-0.2.0-preview.jar:0.2.0-preview]
    at com.microsoft.aad.msal4j.AuthenticationResultSupplier.get(AuthenticationResultSupplier.java:77) [msal4j-0.2.0-preview.jar:0.2.0-preview]
    at com.microsoft.aad.msal4j.AuthenticationResultSupplier.get(AuthenticationResultSupplier.java:37) [msal4j-0.2.0-preview.jar:0.2.0-preview]
    at java.util.concurrent.CompletableFuture$AsyncSupply.run$$$capture(CompletableFuture.java:1590) [?:1.8.0_202]
    at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java) [?:1.8.0_202]
    at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1582) [?:1.8.0_202]
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289) [?:1.8.0_202]
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056) [?:1.8.0_202]
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692) [?:1.8.0_202]
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157) [?:1.8.0_202]
2019-05-20 20:15:26,405 [ERROR] com.microsoft.aad.msal4j.ConfidentialClientApplication [Correlation ID: 55734d67-5e3c-46c3-b819-47ea46892f59] Execution of class com.microsoft.aad.msal4j.AcquireTokenByAuthorizationGrantSupplier failed.
com.microsoft.aad.msal4j.AuthenticationException: {"error_description":"AADSTS50034: The user account 0006BFFD847C5DF5 does not exist in the 8cca582e-0d23-4e13-bf43-30896ccafd4f directory. To sign into this application, the account must be added to the directory.\r\nTrace ID: 381fbfc9-39a1-4345-a8d6-8c7e73e90500\r\nCorrelation ID: 55734d67-5e3c-46c3-b819-47ea46892f59\r\nTimestamp: 2019-05-21 01:15:22Z","error":"invalid_grant"}
    at com.microsoft.aad.msal4j.TokenRequest.executeOauthRequestAndProcessResponse(TokenRequest.java:169) ~[msal4j-0.2.0-preview.jar:0.2.0-preview]
    at com.microsoft.aad.msal4j.ClientApplicationBase.acquireTokenCommon(ClientApplicationBase.java:249) ~[msal4j-0.2.0-preview.jar:0.2.0-preview]
    at com.microsoft.aad.msal4j.AcquireTokenByAuthorizationGrantSupplier.execute(AcquireTokenByAuthorizationGrantSupplier.java:71) ~[msal4j-0.2.0-preview.jar:0.2.0-preview]
    at com.microsoft.aad.msal4j.AuthenticationResultSupplier.get(AuthenticationResultSupplier.java:77) [msal4j-0.2.0-preview.jar:0.2.0-preview]
    at com.microsoft.aad.msal4j.AuthenticationResultSupplier.get(AuthenticationResultSupplier.java:37) [msal4j-0.2.0-preview.jar:0.2.0-preview]
    at java.util.concurrent.CompletableFuture$AsyncSupply.run$$$capture(CompletableFuture.java:1590) [?:1.8.0_202]
    at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java) [?:1.8.0_202]
    at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1582) [?:1.8.0_202]
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289) [?:1.8.0_202]
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056) [?:1.8.0_202]
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692) [?:1.8.0_202]
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157) [?:1.8.0_202]
2019-05-20 20:15:26,408 [ERROR] com.microsoft.aad.msal4j.ConfidentialClientApplication [Correlation ID: 3e9924df-d2f6-48b3-8ec7-0641382cce6b] Execution of class com.microsoft.aad.msal4j.AcquireTokenByAuthorizationGrantSupplier failed.
com.microsoft.aad.msal4j.AuthenticationException: {"error_description":"AADSTS50034: The user account 0006BFFD847C5DF5 does not exist in the 8cca582e-0d23-4e13-bf43-30896ccafd4f directory. To sign into this application, the account must be added to the directory.\r\nTrace ID: 7a611ac4-fbc9-47ac-a482-20faceeb0600\r\nCorrelation ID: 3e9924df-d2f6-48b3-8ec7-0641382cce6b\r\nTimestamp: 2019-05-21 01:15:22Z","error":"invalid_grant"}
    at com.microsoft.aad.msal4j.TokenRequest.executeOauthRequestAndProcessResponse(TokenRequest.java:169) ~[msal4j-0.2.0-preview.jar:0.2.0-preview]
    at com.microsoft.aad.msal4j.ClientApplicationBase.acquireTokenCommon(ClientApplicationBase.java:249) ~[msal4j-0.2.0-preview.jar:0.2.0-preview]
    at com.microsoft.aad.msal4j.AcquireTokenByAuthorizationGrantSupplier.execute(AcquireTokenByAuthorizationGrantSupplier.java:71) ~[msal4j-0.2.0-preview.jar:0.2.0-preview]
    at com.microsoft.aad.msal4j.AuthenticationResultSupplier.get(AuthenticationResultSupplier.java:77) [msal4j-0.2.0-preview.jar:0.2.0-preview]
    at com.microsoft.aad.msal4j.AuthenticationResultSupplier.get(AuthenticationResultSupplier.java:37) [msal4j-0.2.0-preview.jar:0.2.0-preview]
    at java.util.concurrent.CompletableFuture$AsyncSupply.run$$$capture(CompletableFuture.java:1590) [?:1.8.0_202]
    at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java) [?:1.8.0_202]
    at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1582) [?:1.8.0_202]
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289) [?:1.8.0_202]
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056) [?:1.8.0_202]
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692) [?:1.8.0_202]
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157) [?:1.8.0_202]
2019-05-20 20:15:26,416 [ERROR] co.com.accessmanagement.web.security.AADAuthenticationFilter Failed to acquire graph api token.
java.util.concurrent.ExecutionException: com.microsoft.aad.msal4j.AuthenticationException: {"error_description":"AADSTS50034: The user account 0006BFFD847C5DF5 does not exist in the 8cca582e-0d23-4e13-bf43-30896ccafd4f directory. To sign into this application, the account must be added to the directory.\r\nTrace ID: 381fbfc9-39a1-4345-a8d6-8c7e73e90500\r\nCorrelation ID: 55734d67-5e3c-46c3-b819-47ea46892f59\r\nTimestamp: 2019-05-21 01:15:22Z","error":"invalid_grant"}
    at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357) ~[?:1.8.0_202]
    at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895) ~[?:1.8.0_202]
    at co.com.accessmanagement.web.security.AzureADGraphClient.acquireTokenForGraphApi(AzureADGraphClient.java:175) ~[classes/:?]
    at co.com.accessmanagement.web.security.AADAuthenticationFilter.doFilterInternal(AADAuthenticationFilter.java:74) [classes/:?]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116) [spring-security-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:100) [spring-security-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:74) [spring-security-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) [spring-security-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) [spring-security-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) [spring-security-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) [spring-security-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357) [spring-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270) [spring-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.17.jar:9.0.17]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.17.jar:9.0.17]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) [spring-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.17.jar:9.0.17]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.17.jar:9.0.17]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92) [spring-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.17.jar:9.0.17]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.17.jar:9.0.17]
    at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93) [spring-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.17.jar:9.0.17]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.17.jar:9.0.17]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) [spring-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.17.jar:9.0.17]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.17.jar:9.0.17]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:200) [tomcat-embed-core-9.0.17.jar:9.0.17]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.17.jar:9.0.17]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490) [tomcat-embed-core-9.0.17.jar:9.0.17]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.17.jar:9.0.17]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.17.jar:9.0.17]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.17.jar:9.0.17]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.17.jar:9.0.17]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) [tomcat-embed-core-9.0.17.jar:9.0.17]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.17.jar:9.0.17]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:834) [tomcat-embed-core-9.0.17.jar:9.0.17]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1415) [tomcat-embed-core-9.0.17.jar:9.0.17]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.17.jar:9.0.17]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_202]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_202]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.17.jar:9.0.17]
    at java.lang.Thread.run(Thread.java:748) [?:1.8.0_202]

This is the response:

{"timestamp":"2019-05-21T01:15:28.369+0000","status":500,"error":"Internal Server Error","message":"com.microsoft.aad.msal4j.AuthenticationException: {\"error_description\":\"AADSTS50034: The user account 0006BFFD847C5DF5 does not exist in the 8cca582e-0d23-4e13-bf43-30896ccafd4f directory. To sign into this application, the account must be added to the directory.\\r\\nTrace ID: 9c527d88-7965-4f72-bf4f-9d4bb9040600\\r\\nCorrelation ID: 9f312f29-3252-4191-82e7-028814c32d00\\r\\nTimestamp: 2019-05-21 01:15:25Z\",\"error\":\"invalid_grant\"}","path":"/api/country"}

I'm not seeing anything weird in the traffic just the same error calling my API

afrancoc2000 commented 5 years ago

I've tried also with other authorities like this:

And for scopes I've tried:

afrancoc2000 commented 5 years ago

Ok I interrupted the petition this is the request info:

url: https://login.microsoftonline.com/{mytenant}/oauth2/v2.0/token method: POST extraHeaderParams: return-client-request-id: true client-request-id: 0d117f3e-7a0d-43e6-9e4c-b9704f970f93 x-client-CPU: amd64 x-client-VER: 0.2.0-preview x-client-SKU: MSAL.Java Params: scope: https://graph.microsoft.com/user.read requested_token_use: on:behalf_of client_info: 1 grant_type: urn:ietf:params:oauth:grant-type:jwt-bearer assertion: {my-jwt} client-id: {my-clientid} client-secret: {my-clientsecret}

I can see that my Oauth 2.0 token endpoint in the azure portal is: https://login.microsoftonline.com/organizations/oauth2/v2.0/token I'm going to change the authority to https://login.microsoftonline.com/organizations/

jmprieur commented 5 years ago

@afrancoc2000 : what scope did you request on the front end to call your Web API? which user signed-in?

afrancoc2000 commented 5 years ago

@jmprieur I have an angular 5 application and I'm using @azure/msal-angular wrapper.

This is my configuration:

  MsalModule.forRoot({
    clientID: {same-client-id},
    authority: 'https://login.microsoftonline.com/{my-tenant}',
    redirectUri: 'http://localhost:443/',
    cacheLocation : 'localStorage',
    postLogoutRedirectUri: 'http://localhost:443/',
    consentScopes: [
      'user.read',
      'openid',
      'https://myapp.onmicrosoft.com/31621f25-b1dd-4d31-b1d5-38975af8fbc3/user_impersonation',
      'https://graph.microsoft.com/user.read']
})
]

The users I've tried with are both members or guests they authenticate but I haven't been able to use the on behalf of flow. The users are Microsoft account users.

I create my users in the azure portal in Azure Active Directory > Users > new User / new Guest User.

My app is registered in Azure Active Directory > App Registrations / App Registrations (legacy).

Both Id Tokens and Access Tokens are enabled.

This is my manifest:

{
    "id": "11ad51c7-7cb4-4112-8889-1346223f6fdb",
    "acceptMappedClaims": null,
    "accessTokenAcceptedVersion": null,
    "addIns": [],
    "allowPublicClient": false,
    "appId": "8f880461-7ab4-4285-a04c-9b42f6ab0627",
    "appRoles": [],
    "oauth2AllowUrlPathMatching": false,
    "createdDateTime": "2019-04-22T19:02:45Z",
    "groupMembershipClaims": null,
    "identifierUris": [
        "https://myapp.onmicrosoft.com/31621f25-b1dd-4d31-b1d5-38975af8fbc3"
    ],
    "informationalUrls": {
        "termsOfService": null,
        "support": null,
        "privacy": null,
        "marketing": null
    },
    "keyCredentials": [],
    "knownClientApplications": [],
    "logoUrl": null,
    "logoutUrl": "http://myapp.onmicrosoft.com/logout",
    "name": "MyApp",
    "oauth2AllowIdTokenImplicitFlow": true,
    "oauth2AllowImplicitFlow": true,
    "oauth2Permissions": [
        {
            "adminConsentDescription": "Allow the application to access Cuentas on behalf of the signed-in user.",
            "adminConsentDisplayName": "Access MyApp",
            "id": "5c7dab5d-9c0e-440d-8627-5aa0d424cd6a",
            "isEnabled": true,
            "lang": null,
            "origin": "Application",
            "type": "User",
            "userConsentDescription": "Allow the application to access MyApp on your behalf.",
            "userConsentDisplayName": "Access MyApp",
            "value": "user_impersonation"
        }
    ],
    "oauth2RequirePostResponse": false,
    "optionalClaims": null,
    "orgRestrictions": [],
    "parentalControlSettings": {
        "countriesBlockedForMinors": [],
        "legalAgeGroupRule": "Allow"
    },
    "passwordCredentials": [
        {
            "customKeyIdentifier": "XXXXXXXXXXXXXXXXXXXXXXXX",
            "endDate": "2299-12-31T05:00:00Z",
            "keyId": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
            "startDate": "2019-04-22T19:03:59.4564537Z",
            "value": null,
            "createdOn": null,
            "hint": null,
            "displayName": null
        }
    ],
    "preAuthorizedApplications": [
        {
            "appId": "53e81585-229e-497a-b801-87fa9a5da1f6",
            "permissionIds": [
                "5c7dab5d-9c0e-440d-8627-5aa0d424cd6a"
            ]
        },
        {
            "appId": "c0585c9d-d7e7-4a57-9054-97328816eabf",
            "permissionIds": [
                "5c7dab5d-9c0e-440d-8627-5aa0d424cd6a"
            ]
        }
    ],
    "publisherDomain": "myapp.onmicrosoft.com",
    "replyUrlsWithType": [
        {
            "url": "http://localhost:443",
            "type": "Web"
        },
        {
            "url": "http://localhost:8080",
            "type": "Web"
        },
        {
            "url": "http://localhost:8080/login/oauth2/code/azure",
            "type": "Web"
        },
        {
            "url": "http://localhost:4200/admin/pages",
            "type": "Web"
        }
    ],
    "requiredResourceAccess": [
        {
            "resourceAppId": "00000002-0000-0000-c000-000000000000",
            "resourceAccess": [
                {
                    "id": "a42657d6-7f20-40e3-b6f0-cee03008a62a",
                    "type": "Scope"
                },
                {
                    "id": "311a71cc-e848-46a1-bdf8-97ff7156d8e6",
                    "type": "Scope"
                }
            ]
        },
        {
            "resourceAppId": "00000003-0000-0000-c000-000000000000",
            "resourceAccess": [
                {
                    "id": "e1fe6dd8-ba31-4d61-89e7-88639da4683d",
                    "type": "Scope"
                },
                {
                    "id": "7ab1d382-f21e-4acd-a863-ba3e13f7da61",
                    "type": "Role"
                }
            ]
        }
    ],
    "samlMetadataUrl": null,
    "signInUrl": "http://myapp.onmicrosoft.com",
    "signInAudience": "AzureADMultipleOrgs",
    "tags": [],
    "tokenEncryptionKeyId": null
}
jmprieur commented 5 years ago

@afrancoc2000 I see the consentScopes, but not the scope. When you write {same-client-id} do you mean that the Web API and your SPA have the same client ID?

afrancoc2000 commented 5 years ago

@jmprieur yes the API and the frontend app have the same client id, they are both deployed in the same server using an ha-proxy to be exposed with the same url, they are identified by the /api/ path for everithing in the api and everything else is the frontend.

I did registered a different app in my tenant "MyAppFrontend" and authorized it in the API registration using the expose API section, but when I do that I get this different error:

java.util.concurrent.ExecutionException: com.microsoft.aad.msal4j.AuthenticationException: {"error_description":"AADSTS500131: Assertion audience does not match the Client app presenting the assertion. The audience in the assertion was '53e81585-229e-497a-b801-87fa9a5da1f6' and the expected audience is 'https:\/\/myapp.onmicrosoft.com\/31621f25-b1dd-4d31-b1d5-38975af8fbc3' or one of the Application Uris of this application with App ID '8f880461-7ab4-4285-a04c-9b42f6ab0627'(MyApp).\r\nTrace ID: 01f3c4b9-8014-4ec1-b979-973d6d510500\r\nCorrelation ID: 7e9dc973-b9c9-494c-982c-945642c653f8\r\nTimestamp: 2019-05-22 12:56:43Z","error":"invalid_grant","error_uri":"https:\/\/login.microsoftonline.com\/error?code=500131"}

sangonzal commented 5 years ago

@afrancoc2000 You should have two different client ids - one for the front end and one for the backend.

Two observations from browsing the information you have shared:

1) The token you are passing in here public AuthenticationResult acquireTokenForGraphApi(String idToken, String tenantId) should be an access token, and not an Id token. This should be the access token that you requested on the front end, and then included as the bearer token in the request to the back end. This might be what you are already doing (just might have it mislabeled), but it's worth fixing for clarity.

2) In the manifest you shared, "knownClientApplications" is blank. The client Id of the front end application here should be listed here, as described here

I would look over the instructions in that sample, starting with Register the service app (TodoListService-OBO) and ending with Configure the TodoListSPA project. This sample is very similar to your scenario, the front end is the TodoListSPA and the backend is the TodoListService (you can ignore the instructions for the TodoListClient). I think fixing your app and api registrations might help with AADSTS500131.

afrancoc2000 commented 5 years ago

@sangonzal and @jmprieur , thank you for all your help, I added the client Ids to the "knownClientApplications" but still the on behalf of flow gives me problems.

I guess I do have an access token and not an Id one, it is the Bearer token generated from the frontend msal implementation, I thought it was an id token because I use it to create the UserPrincipal and it has all the claims I need for that.

I added some roles in the manifest and I'm getting them in the claims of the Bearer Token, for now I'm going to use them to recreate the UserPrincipal and the authorities and finish the authentication on the backend API without going to the graph API.

I will do more tests and tell you how it goes.

jmprieur commented 5 years ago

@afrancoc2000 if you want to have the same client ID for your client and the web api, I suggest you have a look at this PR: (it's for .NET, but the concepts are the same). https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore-v2/pull/53

Quick question: do you want to let user sign with personal accounts? or only AAD accounts?

afrancoc2000 commented 5 years ago

@jmprieur Thanks I will check it out, I want the user to be able to sign in with both personal and AAD accounts, from my tenant or others.

jfconavarrete commented 5 years ago

Hello @afrancoc2000, hope you're doing alright.

I'm trying to do the same thing, an Angular front end that uses MSAL to login with B2C and then sends HTTP Requests (with an Authorization header) to a Spring Boot API that then validates the token.

The best reference on how to do this was this conversation. Did you manage to get this working? Could you please share your code?

Thanks a lot

jfconavarrete commented 5 years ago

also @afrancoc2000 are you using different client ids for frontend and backend?

Thanks

afrancoc2000 commented 5 years ago

Hi @jfconavarrete I adapted the classes used to create the adal filter in springboot and changed them to use the msal library. I couldn't make the obo flow to work but now I think I know what was the problem in my scope, I was using:

https://myapp.onmicrosoft.com/31621f25-b1dd-4d31-b1d5-38975af8fbc3/user_impersonation

but it should be

https://myapp.onmicrosoft.com/31621f25-b1dd-4d31-b1d5-38975af8fbc3/.default

What I did, that worked was to left the onBehalfOf flow behind and created roles using this article. This way you get a claim with the roles of the user and you don't need to go to microsoft graph to get the groups for the authorities.

I'm preparing a repo with an example but I haven't finished it but you can use the code here as reference for what I did.

afrancoc2000 commented 5 years ago

@jfconavarrete also I used the same client ids for both the frontend and the backend. Hope it helps.

afrancoc2000 commented 5 years ago

@jfconavarrete just another thing, I'm not using the azure active directory B2C service, this is just a multitenant app using the azure active directory registration, but it does work for guest users from other companies and users without a company.

jmprieur commented 5 years ago

@afrancoc2000 : guest accounts work for single tenant applications only. See in the Who can sign-in section.

afrancoc2000 commented 5 years ago

@jmprieur the document says:

Both endpoints also accept sign-ins of guest users of an Azure AD directory for applications configured as single-tenant or for multi-tenant applications configured to point to the tenant-specific endpoint

I have tried it and it does work for guest users, I have a bunch of them authenticating in my app.

jmprieur commented 5 years ago

which document, @afrancoc2000 ?

afrancoc2000 commented 5 years ago

Sorry @jmprieur, The link you posted :)

jmprieur commented 5 years ago

@afrancoc2000 : Is everything working for you? or do you expect anything from us?

afrancoc2000 commented 5 years ago

Hi @jmprieur everything is working for me thanks, I just didn't used the on behalf of flow at the end but I think I found the problem in the scopes, we can close the issue now. I just wanted to do the test with the scopes correction before closing it, but it can be closed.

Thank you very much I really appreciate your help.

jmprieur commented 5 years ago

Thanks for the update. @afrancoc2000. Closing it