So far I followed this post and it helped me so much, however, I now get a "invalid_grant".
Following : https://developer.apple.com/documentation/signinwithapplerestapi/errorresponse I understand that I have an issue either because of the authorization grant or refresh token is invalid.
In despite of my searches and tries (and retries), I am still stuck and I don't know where does it come from. I used the app given at https://developer.apple.com/documentation/authenticationservices/adding_the_sign_in_with_apple_flow_to_your_app
Now that I get my token from the app above, I try to validate it from C# backend but I get a 400 response code invalid_grant.
The only difference I could notice from the post is that I don't have any [Verify] button (option) or [Download] button from the portal compared to the image below. I don't know if this is related but I am trying to provide as much details as I can:
Hopefully someone can help, thanks for any help :) feel free to ask for more details if required
Max
I also had the same issue, I found the solution here:
https://forums.developer.apple.com/thread/118135
as explained in the link, when you are using the code you got from the app, you should use app id instead of service id.
Could you share how you try to create the JWT?
I ve tried a couple of stuff Im at this right know (which doesnt work either, Ill update if I find a real solution)
const string iss = "7#######G"; // team ID
const string aud = "https://appleid.apple.com";
const string sub = "com.######.weblogin"; // serviceid
const string privateKey = "MIGTA#######"; // contents of .p8 file
var d = DateTime.UtcNow.AddDays(-5);
var cngKey = CngKey.Import(
Convert.FromBase64String(privateKey),
CngKeyBlobFormat.Pkcs8PrivateBlob);
var handler = new JwtSecurityTokenHandler();
var securityKey = new ECDsaSecurityKey(new ECDsaCng(cngKey) { KeySize = 256 , HashAlgorithm = CngAlgorithm.ECDsaP256});
securityKey.KeyId = "G#######W";
var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.EcdsaSha256);
return handler.CreateEncodedJwt(iss, aud, new ClaimsIdentity(new List<Claim> { new Claim("sub", sub) }),d, expires: d.AddMonths(3),d, signingCredentials: signingCredentials);
Headers look like that in the jwt, from what Ive gathered there might be the "typ" header which is not present in many implentation, perhaps I shoud get rid of it :
{
"alg": "ES256",
"kid": "G#######W",
"typ": "JWT"
}
body:
{
"sub": "com.#####.weblogin",
"nbf": 1583088895,
"exp": 1591037695,
"iat": 1583088895,
"iss": "7######G",//teamid
"aud": "https://appleid.apple.com"
}
Related
I was looking at the section about Self-issued OpenID Provider Response, where they describe a method of validation, where the public key is included in the token itself. They use this as an example token:
{
"iss": "https://self-issued.me",
"sub": "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs",
"aud": "https://client.example.org/cb",
"nonce": "n-0S6_WzA2Mj",
"exp": 1311281970,
"iat": 1311280970,
"sub_jwk": {
"kty":"RSA",
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx
4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMs
tn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2
QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbI
SD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqb
w0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
"e":"AQAB"
}
}
I get how you can use the appended key for validation. But I don't get what prevents someone from using a fake key-pair to create a similar token. The only way I see this happening is if the public key is known by the validator from somewhere else, but in that case it doesn't make a lot of sense to include it in the token.
How does this work?
The way this work is that the hackers don't have access to the private key that was used to sign the token signature. The public key is derived from the private key.
The public key is safe to distribute as it only verifies the token signature. T
I don't think it's that common to include the public key inside the token as the token size gets bigger. Instead, you, as a receiver, download it separately, or it is provided to you some other way.
The picture below gives a summary of how public-key cryptography works.
But I don't get what prevents someone from using a fake key-pair to create a similar token.
Given the validations in Self-Issued ID Token Validation there is no feasible way for "someone" to sign a token which would have the same sub (JWT Subject). Of course they may sign a Token which would pass validations, but ONLY for a different subject. So IF you choose to accept Self-Issued ID Tokens, the guarantee you get is that you can re-identify the same Subject. That's kind of the whole point, Self-Issued OpenID providers are personal wallets, given the cryptography present in the flow you can be sure a given returned subject is the same one you encountered prior as long as sub is the same as before (or one that you know and have established trust with OOB).
Node.js code as a reference:
import * as assert from "node:assert";
import * as jose from "jose";
let jwt;
const redirect_uri = "https://rp.example.com/siop/cb";
const nonce = "n-0S6_WzA2Mj";
{
// this is unreachable by the party verifying
const kp = await jose.generateKeyPair("ES256");
const sub_jwk = await jose.exportJWK(kp.publicKey);
jwt = await new jose.SignJWT({ sub_jwk, nonce })
.setSubject(await jose.calculateJwkThumbprint(sub_jwk))
.setIssuer("https://self-issued.me")
.setAudience(redirect_uri)
.setProtectedHeader({ alg: "ES256" })
.setExpirationTime("5m")
.setIssuedAt()
.sign(kp.privateKey);
}
const verified = await jose.jwtVerify(
jwt,
async (protectedHeader, token) => {
const { sub_jwk, sub } = JSON.parse(
new TextDecoder().decode(jose.base64url.decode(token.payload))
);
assert.equal(sub === (await jose.calculateJwkThumbprint(sub_jwk)), true);
const key = await jose.importJWK(sub_jwk, protectedHeader.alg);
assert.equal(key.type, "public");
return key;
},
{
audience: redirect_uri,
issuer: "https://self-issued.me",
}
);
assert.equal(verified.payload.nonce, nonce);
console.log(verified);
With the latest update Firebase let you sign in with AppleAuthProvider without using the sign_in_with_apple package. This is really useful but how can I get the full name and email of the user?
I know that I can add scopes with the function .addScope() but then how can I retrieve them?
Just to make an example now we can retrieve those data with the following procedure:
final AuthorizationCredentialAppleID credential =
await SignInWithApple.getAppleIDCredential(
scopes: <AppleIDAuthorizationScopes>[
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
nonce: nonce,
);
final String name = credential.givenName + credential.familyName;
final String email = credential.email;
But now I'd like to something like this
final AppleAuthProvider appleAuth = AppleAuthProvider();
appleAuth.addScope('email');
appleAuth.addScope('name');
final UserCredential authResult = await FirebaseAuth.instance
.signInWithAuthProvider(appleAuth);
final String name = ... //What should I put here?
It Looks like it is a bug
Github issue reported
Hope they fix it as soon as possible
EDIT**********
I found out that this could happen because the first time you used this method, you didn't use scope.
The solution is to go to Settings> iCloud> Password and security> Apps that use the Apple ID.
And delete your app, since Apple only the first time you use the method, is when it brings the information you request
I've this code which is not working.
If i try to post in twitter with the generated access token and access token secret from the page, it works perfectly, but if i try to retrieve the access tokens it doesn't work, it goes to the page, i authorize the app and it doesn't retrieve the tokens, can anyone help me please?
var oauth_consumer_key = "******************************";
var oauth_consumer_secret = "**************************************";
var service = new TwitterService(oauth_consumer_key, oauth_consumer_secret);
OAuthRequestToken requestToken = service.GetRequestToken();
string authUrl = service.GetAuthorizationUri(requestToken).ToString();
Process.Start(authUrl);
TILL THIS POINT THE CODE IS WORKING.
START OF NOT WORKING CODE
OAuthAccessToken accessToken = service.GetAccessToken(requestToken);
string token = accessToken.Token.ToString();
string tokenSecret = accessToken.TokenSecret.ToString();
Console.WriteLine(accessToken.Token);
Console.WriteLine(accessToken.TokenSecret);
Console.ReadKey();
service.AuthenticateWith(accessToken.Token,accessToken.TokenSecret);
This part is working if i authenticate directly with the strings generated in the app page
//service.AuthenticateWith("*****************************", "***********************************");
service.SendTweet(new SendTweetOptions
{
Status = "yayayayayayayayayayyayayay"
});
I really dont understand why it doesn't work, can someone please help me?
Thank you so much guys
I have a web Api, which needs to validate purchases from Play Store. According to what I read in the google api documentation, I need to have a service account, and use its credentials to be authenticated, to be able to do what i want.
I have the following code:
String serviceAccountEmail = "MYSERVICEACCOUNTEMAIL#developer.gserviceaccount.com";
var certificate = new X509Certificate2(#"C:\privatekey.p12", "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { "https://www.googleapis.com/auth/androidpublisher" }
}.FromCertificate(certificate));
// Create the service.
var service = new AndroidPublisherService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential
});
var data = service.Purchases.Get("MYPACKAGE", "MYPRODUCT",
"MYTOKEN")
.Execute();
It throws the following exception
Google.Apis.Requests.RequestError
Invalid Value [400]
Errors [
Message[Invalid Value] Location[ - ] Reason[invalid] Domain[global]
]
I have NO IDEA of what can be causing it. I searched a lot, but I didn't find something really helpful. Any kind help will be really appreciated.
I know this question is "a little" old, but I still want to try to answer for clarification.
var data = service.Purchases.Get("MYPACKAGE", "MYPRODUCT",
"MYTOKEN")
.Execute();
My guess would be that this part is not entirely right. The Purchases class does not have a Get method. Before that you have to specify either (in your case):
service.Purchases.Products.Get(...) -> for consumable products
service.Purchases.Subscriptions.Get(...) -> for subscriptions
This answer assumes you are using V2. Hope it helps anyone who is stuck.
I've been trying to integrate the Instagram API in my app, but am stuck with the authentication. I had it working completely fine when I was just using the implicit flow version which gave me the access_token as part of the URI fragment.
However, now I'm changing to the server-side flow, in which I receive a code after the user logs in. I then post this code to the access token URL, which will then give me the access_token as well as certain information about the user, such as their username and profile picture link.
I am using the InstaSharp library, modifying the source code.
HttpClient client = new HttpClient { BaseAddress = new Uri(config.OAuthUri + "access_token/", UriKind.Absolute) };
var request = new HttpRequestMessage(HttpMethod.Post, client.BaseAddress);
request.AddParameter("client_secret", config.ClientSecret);
request.AddParameter("client_id", config.ClientId);
request.AddParameter("grant_type", "authorization_code");
request.AddParameter("redirect_uri", config.RedirectUri);
request.AddParameter("code", code);
return client.ExecuteAsync<OAuthResponse>(request);
After creating my request, it is formatted as so:
{Method: POST, RequestUri: 'https://api.instagram.com/oauth/access_token/?client_secret={CLIENT_SECRET}&client_id={CLIENT_ID}&grant_type=authorization_code&redirect_uri=http://instagram.com &code={CODE}', Version: 1.1, Content: , Headers: { }}
(I inserted the space between the redirect_uri and code because it wouldn't let me post the question otherwise)
Everything appears normal in the address, but I always receive an error in the retuned json file:
"{"code": 400, "error_type": "OAuthException", "error_message": "You must provide a client_id"}"
I have no clue what is causing this error. Any help is greatly appreciated!
Thanks!
Elliott
Are you using the latest version of InstaSharp? Fork it here. You can check the README.md there although it's a bit outdated and you need to tweak some config. Here's how you can do it with the latest version that is in github:
// create the configuration in a place where it's more appropriate in your app
InstaSharpConfig = new InstagramConfig(
apiURI, oauthURI, clientId, clientSecret, redirectUri);
// then here's a sample method you can have to initiate auth
// and catch the redirect from Instagram
public ActionResult instagramauth(string code)
{
if (string.IsNullOrWhiteSpace(code))
{
var scopes = new List<InstaSharp.Auth.Scope>();
scopes.Add(InstaSharp.Auth.Scope.likes);
var link = InstaSharp.Auth.AuthLink(
oauthURI, clientId, redirectUri, scopes);
// where:
// oauthURI is https://api.instagram.com/oauth
// clientId is in your Instagram account
// redirectUri is the one you set in your Instagram account;
// for ex: http://yourdomain.com/instagramauth
return Redirect(link);
}
// add this code to the auth object
var auth = new InstaSharp.Auth(InstaSharpConfig);
// now we have to call back to instagram and include the code they gave us
// along with our client secret
var oauthResponse = auth.RequestToken(code);
// save oauthResponse in session or database, whatever suits your case
// oauthResponse contains the field Access_Token (self-explanatory),
// and "User" that'll give you the user's full name, id,
// profile pic and username
return RedirectToAction("action", "controller");
}
Take note that you can split up the "instagramauth" method. Did it that way for brevity.