Search code examples
springazurespring-bootoauth-2.0single-sign-on

Spring Security Microsoft Oauth2 Login Errors


I'm attempting to access Microsoft Account oauth without any Azure AD accounts, but I am receiving an unauthorized_client error before the redirect back to my app.

Here is my yml configuration for spring security:

spring:
  security:
    oauth2:
      client:
        registration:
          microsoft:
            client-id: [my app registration client id]
            client-secret: [my app registration secret id]
            scope: profile, openid, https://graph.microsoft.com/User.Read
            client-name: Microsoft
            authorization-grant-type: authorization_code
            provider: microsoft
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
        provider:
          microsoft:
            authorization-uri: https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize
            key-set-uri: https://login.microsoftonline.com/consumers/discovery/v2.0/keys
            token-uri: https://login.microsoftonline.com/consumers/oauth2/v2.0/token
            user-info-uri: https://graph.microsoft.com/oidc/userinfo
            userNameAttribute: sub
            issuer-uri: https://login.microsoftonline.com/${app.oauth2.tenant-id}/v2.0

app:
  oauth2:
    tenant-id: [my tenant id]

Client and secret ids match within both yml and azure portal. SSO fails both locally and when deployed. Is there any additional configuration I need to do here in order to get this working or do default Microsoft clients just not work whatsoever with java oauth?


Solution

  • There are actually several "features" in Microsoft's SSO implementation preventing OOTB functionality.

    Issue 1: Token contains an extra nonce that violates the JWT specification

    Resolution: Create a new scope under Expose an API. This scope can be named anything. Add this scope into your API Permissions section and update your scope property for the client registration to include the new scope.

    More information on this issue can be found here: https://xsreality.medium.com/making-azure-ad-oidc-compliant-5734b70c43ff

    Issue 2: Invalid scopes

    Resolution: Don't include any of the graph api scopes. They currently do not work for Microsoft accounts (as of 2/16/21).

    Issue 3: Invalid Issuer

    Resolution: Use the issuer that Microsoft is currently exposing under their well known endpoints api for whichever api you are using.

    Consumer: https://login.microsoftonline.com/consumers/v2.0/.well-known/openid-configuration

    Common: https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration

    I don't believe common will currently ever work since only consumer endpoints have a tenantId and that is included in the documented issuer string (as of 2/16/21), but the api has been in flux for the consumer endpoints with several different bugs. On 2/15/21, the issuer was hardcoded to "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0" regardless of tenantId and as documented in the above linked article, it was "https://sts.windows.net/407b9272-20f5-421c-a25f-e7a189309c4b/" as of 8/21/19. Currently (2/16/21), the issuer correctly includes the tenantId.

    *** Update 2/18/21: despite the well-known endpoint saying the issuer accepts a tenant id, the issuer is still hard-coded to that tenantId I posted above.

    Resulting YAML:

    spring:
      security:
        oauth2:
          client:
            registration:
              microsoft:
                client-id: [my app registration client id]
                client-secret: [my app registration secret id]
                scope: profile, openid, [my custom scope with the full api prefix]
                client-name: Microsoft
                authorization-grant-type: authorization_code
                provider: microsoft
                redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
            provider:
              microsoft:
                authorization-uri: https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize
                key-set-uri: https://login.microsoftonline.com/consumers/discovery/v2.0/keys
                token-uri: https://login.microsoftonline.com/consumers/oauth2/v2.0/token
                user-info-uri: https://graph.microsoft.com/oidc/userinfo
                userNameAttribute: sub
                issuer-uri: https://login.microsoftonline.com/${app.oauth2.tenant-id}/v2.0
    
    app:
      oauth2:
        tenant-id: [my tenant id]
    

    For the value [my custom scope with the full api prefix], make sure to copy the full scope name including the api://[app id].