MicrosoftDocs / azure-docs

Open source documentation of Microsoft Azure
https://docs.microsoft.com/azure
Creative Commons Attribution 4.0 International
10.25k stars 21.42k forks source link

Integrate App service with Sign-in with apple #47611

Closed m-andersen closed 4 years ago

m-andersen commented 4 years ago

Identify providers are easy to add but we have big problems trying to find out how to add Sign-in with Apple, which is now a requirement for all new apps. This link describes Azure AD B2C, but is that the same as Azure Active Directory? And how to link all this together like with Facebook. https://github.com/azure-ad-b2c/samples/tree/master/policies/sign-in-with-apple

I would like to eventually see the a token and sid:xxx from EasyAuth with Sign-in with apple. Is that possible to have all these providers be compatible or what should we expect?


Document Details

Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

HeinA commented 2 years ago

@HeinA which authentication flow are you using? At what point are you getting the 401?

Got it sorted out. It was a miconfiguration on the App Service side.

Thanx for your prompt response though!

Regards

HeinA commented 2 years ago

Hey @RyanHill-MSFT

I'm now also having difficulty in refreshing the Apple token. May I also contact you at the above mentioned email address to resolve this issue?

Thanx

RyanHill-MSFT commented 2 years ago

Hey @RyanHill-MSFT

I'm now also having difficulty in refreshing the Apple token. May I also contact you at the above mentioned email address to resolve this issue?

Thanx

Yes you can @HeinA

StuartFeldt commented 2 years ago

@gfaraj I've reached out to the product team for any updates. EDIT: The token refresh isn't supported with generic OIDC, but the team is working on a solution to address this limitation. When more details are available, we'll share them. /cc @mattchenderson

Hi @RyanHill-MSFT -- do we have any updates on the short-lived tokens when using the Apple OIDC provider?

@RyanHill-MSFT Any update on token refresh for Apple OIDC provider?

masonmc commented 1 year ago

some notes for others still struggling:

this definitely worked for me. I was getting a 401 ("you do not have permission to view this page") when I passed the token I got from the Apple API calls up to /.auth/login/apple.

My 401 was caused by an incorrect Apple Client Secret JWT (the JWT you construct using the p8 you downloaded from apple). Remember, these expire every 6 months, so if it was working, and now it's broke, that might be why!

Follow the instructions at: https://learn.microsoft.com/en-us/azure/app-service/configure-authentication-provider-apple

To generate the JWT you'll need some code, your team ID, your key ID, your p8, and your client ID.

Your Team ID and KeyId are these:

image

Make a new C# console app, and enable (fancy! new!) immediate execution (https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/program-structure/top-level-statements).

Use this sample code (this is the JWT generation sample code from the above link, + I added some Console.WriteLine() calls):

using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;

void GetAppleClientSecret(string teamId, string clientId, string keyId, string p8key)
{
    string audience = "https://appleid.apple.com";

    string issuer = teamId;
    string subject = clientId;
    string kid = keyId;

    IList<Claim> claims = new List<Claim> {
        new Claim ("sub", subject)
    };

    CngKey cngKey = CngKey.Import(Convert.FromBase64String(p8key), CngKeyBlobFormat.Pkcs8PrivateBlob);

    SigningCredentials signingCred = new SigningCredentials(
        new ECDsaSecurityKey(new ECDsaCng(cngKey)),
        SecurityAlgorithms.EcdsaSha256
    );

    JwtSecurityToken token = new JwtSecurityToken(
        issuer,
        audience,
        claims,
        DateTime.Now,
        DateTime.Now.AddDays(180),
        signingCred
    );
    token.Header.Add("kid", kid);
    token.Header.Remove("typ");

    Console.WriteLine($"token:\n\n{token}");
    JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();

    Console.WriteLine("\n\n");
    Console.WriteLine("\n----- done.  Put the following into Azure, client secret setting:\n\n");
    Console.WriteLine(tokenHandler.WriteToken(token));
}

GetAppleClientSecret(
    teamId: "(see above, the 10-character ID in the top-right of developer.apple.com, next to your name)",
    clientId: "(your client ID.  I used the ID of my actual app (com.xxx.xxxapp).  I didn't use the services ID.  I'm not sure it matters, so long as what's specified here matches what's configured in the azure portal.)",
    keyId: "(see above. This key should have Sign in with Apple as an Enabled Service)",
    p8key: "(you downloaded this from apple, default filename is AuthKey_(keyId).p8.  Paste in the chars between the ---BEGIN and ---END PRIVATE KEY, no line breaks.)"
);

Console.ReadLine(); // so the console stays up, allowing you to copy the key out!

I'm using these nugets:

image

The JWT you need is exactly that last outputted string (it'll start with eyJ... probably), with NO LINE BREAKS:

image

(By the way, you can paste that JWT into https://www.jstoolset.com/jwt to verify it and make note of the expiration date).

Next, go to the configuration page of your mobile app in azure, and create a new application setting. Name it anything, I named mine "signInWithAppleClientSecret", and I set its value to the JWT (again, careful there's no line breaks, leading/trailing spaces, etc. It must be exact).

image

Don't forget to hit save, up top!

image

Then, go to the Authentication page of your mobile app in azure, and Add Provider, Apple.

image

Your client ID in this config must match the clientID in the JWT (aka the 2nd argument to GetAppleClientSecret()). For "Client Secret Setting Name," choose the application setting you just made (i.e. signInWithAppleClientSecret).

image

RESTART your azure app.

On the client side, the correct way to pass what you get from the Apple SDK is, as others have pointed out, via id_token. This little detail is missing from the docs.

{"id_token": (identityToken) }

where (identityToken) is the "IdentityToken" property of the ASAuthorizationAppleIdCredential you (hopefully!) got back from your call to ASAuthorization's GetCredential method (inside your DidComplete).

Here's my (chopped up!) client side code, confirmed working:

public class SignInWithAppleLoginResults
    {
        public bool IsSuccess;
        public string Message { get; set; }
        public string IdToken { get; set; }
    }

.....
public async Task<SignInWithAppleLoginResults> SignInWithAppleAsync()
{
                _signInWithAppleCompletionTask = new TaskCompletionSource<SignInWithAppleLoginResults>();

                var appleIdProvider = new ASAuthorizationAppleIdProvider();
                var request = appleIdProvider.CreateRequest();
                request.RequestedScopes = new[] { ASAuthorizationScope.Email, ASAuthorizationScope.FullName };

                var authorizationController = new ASAuthorizationController(new ASAuthorizationRequest[] { request });
                authorizationController.Delegate = this;
                authorizationController.PresentationContextProvider = this;
                authorizationController.PerformRequests();

                var appleAuth = await _signInWithAppleCompletionTask.Task;

                if (appleAuth.IsSuccess == false)
                {
                    throw new Exception(appleAuth.Message);
                }

                // now we have the apple token... give it to azure to finish off the login.
                JObject zumoPayload = new JObject
                {
                    ["id_token"] = appleAuth.IdToken
                };

                var signedInUser = await MobileService.LoginAsync("apple", zumoPayload);
                if (signedInUser != null) Log.Information($"you are now signed-in as {signedInUser.UserId}"); 
                // UserId will be sid:xxxxx..., and you'll have an azure token!

                ..... // other app-specific code to get real name etc from azure, snipped
}

And then the didComplete, for the Apple SDK:


        [Export("authorizationController:didCompleteWithAuthorization:")]
        public void DidComplete(ASAuthorizationController controller, ASAuthorization authorization)
        {
            var results = new SignInWithAppleLoginResults()

            if (authorization.GetCredential<ASAuthorizationAppleIdCredential>() is ASAuthorizationAppleIdCredential appleIdCredential)
            {
                results.IdToken = appleIdCredential.IdentityToken.ToString();
                results.IsSuccess = true;
                results.Message = "Logged in!"; // this is specific to my app, ignore.
            }
            else 
            { 
                results.IsSuccess = false,
                results.Message = "Did not receive Apple ID credentials";
            }
            _signInWithAppleCompletionTask.TrySetResult(results);
        }

        [Export("authorizationController:didCompleteWithError:")]
        public void DidComplete(ASAuthorizationController controller, NSError error)
        {
            Log.Error($"DidComplete (apple sign in): error: {error.ToString()}");

            _signInWithAppleCompletionTask.TrySetResult(new SignInWithAppleLoginResults()
            {
                IsSuccess = false,
                Message = error.ToString()
            });
        }

I hope it helps!

burrowj commented 10 months ago

@masonmc did you get token refresh working for apple? my tokens are lasting 1 day