When implementing Stripe in Android there is CardInputWidget which gives you a Card object, then you get a token from Stripe API using that card and finally you send that token to your server, which makes the charge.
When implementing Stripe in iOS I can see that the workflow is quite different. The server needs to have an API endpoint to provide Stripe ephemeral key. Is there any way to do it like in Android workflow - without ephemeral key?
let stripeCard = STPCardParams() /// Declare Stripe Payment Function
stripeCard.name = "Card Name" // you can enter Card Owner name which is displayed in card
stripeCard.number = "Card number" //You can enter card Number which is displayed in Card
stripeCard.expMonth = "ExpireMonth" // enter expire Month which is displayed in Card
stripeCard.expYear = "ExpireYear" // enter expire year which is displayed in Card
stripeCard.cvc = "CVV" // enter CVV which is displayed in Card
//after check card valid or not from below method
if STPCardValidator.validationState(forCard: self.stripeCard) == .valid
{
// the card is valid.
print("Valid card")
STPAPIClient.shared().createToken(withCard: self.stripeCard, completion: { (token, error) -> Void in
if error != nil {
print(error ?? "")
return
}
print(token!)
// call your php Api Pass token id which is given bellow link PHP API link
APICallResponse.shared.getStripePayment(arrUserLoginDetails: self.arrStripePayement,vc:self, completion: {data in
self.SkripePayment = data
})
}
}
You can take reference of PHP API from given link
Stripe payment with php
Yes, absolutely, you can develop with Stripe's iOS SDK without using their pre-built UI or ephemeral key method.
You can use your own form or the STPPaymentCardTextField class, create a STPCardParams instance, and then create a STPToken from that which you can send off to your backend.
STPCardParams *cardParams = [[STPCardParams alloc] init];
cardParams.number = #"4242424242424242";
cardParams.expMonth = 10;
cardParams.expYear = 2020;
cardParams.cvc = #"345";
[[STPAPIClient sharedClient] createTokenWithCard:cardParams completion:^(STPToken *token, NSError *error) {
...
}
}];
See https://stripe.com/docs/mobile/ios/custom#stpapiclient--stpcardparams for more.
Related
I'm developing Apple Authentication feature on Android with React Native, using this library: https://github.com/invertase/react-native-apple-authentication. Everything goes fine, but there is still a thing I want to show in the form is that the real email, or Apple ID of the user. The default settings of Apple accounts is that use private relay, so after I call signIn() method in this code fragment
// App.js
import { appleAuthAndroid } from '#invertase/react-native-apple-authentication';
import 'react-native-get-random-values';
import { v4 as uuid } from 'uuid'
async function onAppleButtonPress() {
// Generate secure, random values for state and nonce
const rawNonce = uuid();
const state = uuid();
// Configure the request
appleAuthAndroid.configure({
// The Service ID you registered with Apple
clientId: 'com.example.client-android',
// Return URL added to your Apple dev console. We intercept this redirect, but it must still match
// the URL you provided to Apple. It can be an empty route on your backend as it's never called.
redirectUri: 'https://example.com/auth/callback',
// The type of response requested - code, id_token, or both.
responseType: appleAuthAndroid.ResponseType.ALL,
// The amount of user information requested from Apple.
scope: appleAuthAndroid.Scope.ALL,
// Random nonce value that will be SHA256 hashed before sending to Apple.
nonce: rawNonce,
// Unique state value used to prevent CSRF attacks. A UUID will be generated if nothing is provided.
state,
});
// Open the browser window for user sign in
const response = await appleAuthAndroid.signIn();
// Send the authorization code to your backend for verification
}
I got an id_token, after I decode the token, I got an object in this pattern:
{
"aud":"",
"auth_time":,
"c_hash":"xxxxxxx",
"email":"xxxxxxx#privaterelay.appleid.com",
"email_verified":"true",
"exp":1663743691,
"iat":1663657291,
"is_private_email":"true",
"iss":"https://appleid.apple.com",
"nonce":"xxxxxxxxxxxxxxxxxx",
"nonce_supported":true,
"sub":"xxxxxxxxxxxxxxxx"
}
whose the email is not the real email that the user entered before. So this can cause a confusion after that when I show the user's information in a form to confirmation, I can only use this private relay email. I wonder that whether any way to decode this email to get the real one, by using c_hash for instance.
The Account Kit documentation states that if your began the login session with AccountKitActivity.ResponseType.TOKEN, it's possible to access the Account Kit ID, phone number and email of the current account via a call to getCurrentAccount().
Is it possible to get the user's phone number if you began with AccountKitActivity.ResponseType.CODE just like the way Saavn does it?
Yes, it's possible provided you use LoginType.PHONE in your configuration.
AccountKit.getCurrentAccount(new AccountKitCallback<Account>() {
#Override
public void onSuccess(final Account account) {
String accountKitId = account.getId();
PhoneNumber phoneNumber = account.getPhoneNumber();
String phoneNumberString = phoneNumber.toString();
}
#Override
public void onError(final AccountKitError error) {
// Handle Error
}
});
This is your phone number: phoneNumberString; but, account.getEmail() will return null if LoginType.PHONE was used in your configuration.
Vice versa if you use LoginType.EMAIL in your configuration.
The purpose of using CODE instead of TOKEN is to shift the token request to your application server. The server uses the Graph api to submit the auth token and if the auth token is valid, the call returns the access token which is then used to verify the user's identity for subsequent API calls.
A graph call to validate the access token returns the account kit id plus additional metadata like the associated phone number and/or email.
{
"id":"12345",
"phone":{
"number":"+15551234567"
"country_prefix": "1",
"national_number": "5551234567"
}
}
You can fetch Account ID,Email and Phone number using below code:
let accountKit = AKFAccountKit(responseType: AKFResponseType.accessToken)
accountKit.requestAccount { (account, error) in
if(error != nil){
//error while fetching information
}else{
print("Account ID \(account?.accountID)")
if let email = account?.emailAddress,email.characters.count > 0{
print("Email\(email)")
}else if let phoneNum = account?.phoneNumber{
print("Phone Number\(phoneNum.stringRepresentation())")
}
}
}
If you have access code/token...
On server or client, you can exchange access token for mobile number and country code with this FB AccountKit API - https://graph.accountkit.com/v1.1/me/?access_token=xxxxxxxxxxxx. Here xxxxxxxxxx is your Access Token.
If you have auth code/token instead...
You can first exchange the access code for an access token on the server side (because it contains the App Secret) with this API - https://graph.accountkit.com/v1.1/access_token?grant_type=authorization_code&code=xxxxxxxxxx&access_token=AA|yyyyyyyyyy|zzzzzzzzzz. Here xxxxxxxxxx, yyyyyyyyyy and zzzzzzzzzz are the auth code, app id and app secret respectively. Once you have the access token with it, you can get the mobile number with the above mentioned API.
Good Luck.
I've been wracking my brain these past two days to try and understand how to use the authentication built into ASP.NET's WebAPI 2 using Google as an external authentication, and not being familiar with OAuth 2, I'm quite lost. I have followed this tutorial to set up the sign-in button on my Android client and send the "idToken" to the Web API. I've also followed this (now out of date) tutorial on setting up Google as an external login.
The problem happens when I try to send it I get {"error":"unsupported_grant_type"} as a response. Some other tutorials lead me to believe that the POST to mysite.com/token does not contain the correct data. This means I'm either building the request incorrectlyon the client, I'm somehow handling it incorrectly on the backend, I'm sending it to the wrong url, or I'm doing something entirely else wrong.
I found this SO answer which says to get a URL from /api/Accounts/ExternalLogins, but the sign-in button already gives me the access token that would supply to me (if I understand that correctly).
If someone could help me out here on what the exact process should be from start to finish, that would be amazing.
UPDATE: Okay, so here are some things that I've learned since I asked this question.
website.com/token URI is the redirect for the built in OAuth server in the WebAPI2 template. This is not useful for this particular problem.
The id_token is an encoded JWT token.
The website.com/signin-google URI is the redirect for normal Google login, but does not accept these tokens.
I may have to write my own AuthenticationFilter that uses the Google Client library to authorize through the Google API.
UPDATE 2: I'm still working on getting this AuthenticationFilter Implementation. Things seem to be going well at this point, but I'm getting stuck on some things. I've been using this example to get the token verification code, and this tutorial to get the AuthenticationFilter code. The result is a mix of both of them. I'll post it here as an answer once it's complete.
Here are my current problems:
Producing an IPrincipal as output. The verification example makes a ClaimPrincipal, but the AuthenticationFilter example code uses a UserManager to match the username to an existing user and returns that principal. The ClaimsPrincipal as created in the verification example directly does not auto-associate with the existing user, so I need to attempt to match some element of the claims to an existing user. So how do I do that?
I still have an incomplete idea of what a proper flow for this is. I'm currently using the Authentication header to pass my id_token string using a custom scheme: "goog_id_token". The client must send their id_token for every method called on the API with this custom AuthenticationFilter. I have no idea how this would usually be done in a professional environment. It seems like a common enough use case that there would be tons of information about it, but I haven't seen it. I have seen the normal OAuth2 flow, and since I'm only using an ID Token, and not an Access Token I'm a bit lost on what an ID Token is supposed to be used for, where it falls in a flow, and where it's supposed to live in an HTTP packet. And because I didn't know these things, I've kind of been making it up as I go along.
Wow, I did it. I figured it out. I... I can't believe it.
As metioned in my question Update 2, this code is assembled from Google's official API C# example and Microsoft's Custom AuthenticationFilter tutorial and code example. I'm going to paste the AuthorizeAsync() here and go over what each block of code does. If you think you see an issue, please feel free to mention it.
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
bool token_valid = false;
HttpRequestMessage request = context.Request;
// 1. Look for credentials in the request
//Trace.TraceInformation(request.ToString());
string idToken = request.Headers.Authorization.Parameter.ToString();
The client adds the Authorization header field with the scheme followed by a single space, followed by the id token. It looks something like Authorization: id-token-goog IaMS0m3.Tok3nteXt.... Putting the ID token in the body as given in the google documentation made no sense in this filter so I decided to put it in the header. For some reason it was difficult to pull custom headers from the HTTP packets so I just decided to use the Authorization header with a custom scheme followed by the ID token.
// 2. If there are no credentials, do nothing.
if (idToken == null)
{
Trace.TraceInformation("No credentials.");
return;
}
// 3. If there are credentials, but the filter does not recognize
// the authentication scheme, do nothing.
if (request.Headers.Authorization.Scheme != "id-token-goog")
// Replace this with a more succinct Scheme title.
{
Trace.TraceInformation("Bad scheme.");
return;
}
This whole point of a filter is to ignore requests that the filter doesn't govern (unfamiliar auth schemes, etc), and make judgement on requests that it's supposed to govern. Allow valid authentication to pass to the downstream AuthorizeFilter or directly to the Controller.
I made up the scheme "id-token-goog" because I had no idea if there was an existing scheme for this use case. If there is, somebody please let me know and I'll fix it. I guess it doesn't particularly matter at the moment as long as my clients all know the scheme.
// 4. If there are credentials that the filter understands, try to validate them.
if (idToken != null)
{
JwtSecurityToken token = new JwtSecurityToken(idToken);
JwtSecurityTokenHandler jsth = new JwtSecurityTokenHandler();
// Configure validation
Byte[][] certBytes = getCertBytes();
Dictionary<String, X509Certificate2> certificates =
new Dictionary<String, X509Certificate2>();
for (int i = 0; i < certBytes.Length; i++)
{
X509Certificate2 certificate =
new X509Certificate2(certBytes[i]);
certificates.Add(certificate.Thumbprint, certificate);
}
{
// Set up token validation
TokenValidationParameters tvp = new TokenValidationParameters()
{
ValidateActor = false, // check the profile ID
ValidateAudience =
(CLIENT_ID != ConfigurationManager
.AppSettings["GoogClientID"]), // check the client ID
ValidAudience = CLIENT_ID,
ValidateIssuer = true, // check token came from Google
ValidIssuer = "accounts.google.com",
ValidateIssuerSigningKey = true,
RequireSignedTokens = true,
CertificateValidator = X509CertificateValidator.None,
IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
{
return identifier.Select(x =>
{
// TODO: Consider returning null here if you have case sensitive JWTs.
/*if (!certificates.ContainsKey(x.Id))
{
return new X509SecurityKey(certificates[x.Id]);
}*/
if (certificates.ContainsKey(x.Id.ToUpper()))
{
return new X509SecurityKey(certificates[x.Id.ToUpper()]);
}
return null;
}).First(x => x != null);
},
ValidateLifetime = true,
RequireExpirationTime = true,
ClockSkew = TimeSpan.FromHours(13)
};
This is all unchanged from the Google example. I have almost no idea what it does. This basically does some magic in creating a JWTSecurityToken, a parsed, decoded version of the token string, and sets up the validation parameters. I'm not sure why the bottom portion of this section is in it's own statement block, but it has something to do with the CLIENT_ID and that comparison. I'm not sure when or why the value of CLIENT_ID would ever change, but apparently it's necessary...
try
{
// Validate using the provider
SecurityToken validatedToken;
ClaimsPrincipal cp = jsth.ValidateToken(idToken, tvp, out validatedToken);
if (cp != null)
{
cancellationToken.ThrowIfCancellationRequested();
ApplicationUserManager um =
context
.Request
.GetOwinContext()
.GetUserManager<ApplicationUserManager>();
Get the user manager from the OWIN context. I had to dig around in context intellisense until I found GetOwinCOntext(), and then found that I had to add using Microsoft.Aspnet.Identity.Owin; in order to add the partial class that included the method GetUserManager<>().
ApplicationUser au =
await um
.FindAsync(
new UserLoginInfo(
"Google",
token.Subject)
);
This was the very last thing I had to fix. Again, I had to dig through um Intellisense to find all of the Find functions and their overrides. I had noticed from the Identity Framework-created tables in my database that there is one called UserLogin, whose rows contain a provider, a provider key, and a user FK. The FindAsync() takes a UserLoginInfo object, which contains only a provider string and a provider key. I had a hunch that these two things were now related. I had also recalled that there was a field in the token format that included a key-looking field that was a long number that started with a 1.
validatedToken seems to be basically empty, not null, but an empty SecurityToken. This is why I use token instead of validatedToken. I'm thinking there must be something wrong with this, but since the cp is not null, which is a valid check for a failed validation, it makes enough sense that the original token is valid.
// If there is no user with those credentials, return
if (au == null)
{
return;
}
ClaimsIdentity identity =
await um
.ClaimsIdentityFactory
.CreateAsync(um, au, "Google");
context.Principal = new ClaimsPrincipal(identity);
token_valid = true;
Here I have to create a new ClaimsPrincipal since the one created above in validation is empty (apparently that's correct). Took a guess on what the third parameter of CreateAsync() should be. It seems to work that way.
}
}
catch (Exception e)
{
// Multiple certificates are tested.
if (token_valid != true)
{
Trace.TraceInformation("Invalid ID Token.");
context.ErrorResult =
new AuthenticationFailureResult(
"Invalid ID Token.", request);
}
if (e.Message.IndexOf("The token is expired") > 0)
{
// TODO: Check current time in the exception for clock skew.
Trace.TraceInformation("The token is expired.");
context.ErrorResult =
new AuthenticationFailureResult(
"Token is expired.", request);
}
Trace.TraceError("Error occurred: " + e.ToString());
}
}
}
}
The rest is just exception catching.
Thanks for checking this out. Hopefully you can look at my sources and see which components came from which codebase.
I have an ASP.NET WebApi 2.1 application with OAuth2 configured. I have and Android client where I can do authentication by using the following methods:
WebView approach (Web Api External Providers): redirect to https://www.facebook.com/dialog/oauth..., user do login there, FB asks for permissions, redirects to my url, catch it, access token got, done.
Facebook SDK approach: under the hood it does: redirect to https://graph.facebook.com/oauth..., user do login there, FB asks for permissions, redirects to my url, catch it, access token got, done.
The problem is, if I go with the WebView version, the token is good for authorizing user in my Web Api application, but I cannot call Graph API by using it, I receive OAuthException 190 (no subcode).
But if I do the SDK authorization, Graph API is accessible (through the Android Facebook SDK), but using the token I've got from it, Web Api authorization is not working, I get 401 by calling Authorization/UserInfo.
So my question are the above token types interchangeable somehow?
Any help would be greatly appreciated.
Sorry if that was not clear, I'm using Web Api w/ ASP.NET Identity 2.0 template, so OAuth plumbing code is already present there.
I was able to find an answer to my own question, let me share it with you.
So the problem is that the token I've got from the Facebook's OAuth dialog after the redirect is not the same token that my application can use to call Facebook Graph APIs in the name of the actual user. That Graph API token is reachable at the following point:
Assume you are using the mentioned template above, you can find App_Data/Startup.Auth.cs class with definition of a FacebookAuthenticationOptions instance. There you can catch the API token and can persist that into the database. For example:
var fbopts = new FacebookAuthenticationOptions
{
AppId = Global.Config.ExternalServices.FacebookAppID,
AppSecret = Global.Config.ExternalServices.FacebookAppSecret,
Scope = { "email", "user_friends", "publish_actions" },
Provider = new FacebookAuthenticationProvider
{
OnAuthenticated = async context =>
{
// This token will be OK for calling Graph API
string accessToken = context.AccessToken;
using (var tracer = Global.Tracer.CreateBuilder())
{
try
{
tracer.InformationLine("Storing Facebook OAuth token: " + accessToken);
string fbUserID = context.Identity.GetUserId();
string fbUserName = context.Identity.Name;
tracer.InformationLine("Facebook User ID: " + fbUserID);
tracer.InformationLine("Facebook User Name: " + fbUserName);
// Store it into the db
// assume Task StoreOAuthToken(string providerName, string providerKey, string accessToken) is defined
await StoreOAuthToken("Facebook", fbUserID, accessToken);
}
catch (Exception ex)
{
tracer.ErrorLine("Failed.", ex);
}
}
}
}
};
app.UseFacebookAuthentication(fbopts);
At this point you're gonna have a row in a table that consists of the following columns:
OAuthAccessToken.ProviderName
OAuthAccessToken.ProviderKey
OAuthAccessToken.AccessToken
Now you can provide an API to your consumers to have that API token for calling Graph API, like:
[Route("AccessTokens")]
[Authorize]
public async Task<List<OAuthAccessToken>> GetAccessTokens(string providerName = null)
{
var userID = User.Identity.GetUserId();
var q = from l in this.Context.AspNetUserLogins // Managed by ASP.NET Identity 2.0
from t in this.Context.OAuthAccessTokens // Stored by you with above code
where l.UserId == userID && t.ProviderName == l.LoginProvider && t.ProviderKey == l.ProviderKey
select t;
if (!string.IsNullOrEmpty(providerName)) q = q.Where(t => t.ProviderName == providerName);
return await q.ToListAsync();
}
So on Android after doing a Facebook login I have the Bearer token for my application's Web Api calls, and I can get my token for accessing Graph API by calling the action above.
Maybe there are easier methods for achieving the above. Please let me know if you find any.
I'm developing a Sinatra app for which I'd like to use OmniAuth. So far, I have something similar to this for the web app:
http://codebiff.com/omniauth-with-sinatra
I'd like the web app to be usable via Android phones which would use an API, authenticating by means of a token. The development of an API seems to be covered nicely here:
Sinatra - API - Authentication
What is not clear is now I might arrange the login procedure. Presumably it would be along these lines:
User selects what service to use, e.g. Twitter, FaceBook &c., by means of an in-app button on the Android device.
The Android app opens a webview to log in to the web app.
A token is somehow created, stored in the web app's database, and returned to the Android app so that it can be stored and used for subsequent API requests.
I'm not very clear on how point 3 might be managed - does anyone have any suggestions?
As no-one seems to have any suggestions, here's what I've come up with so far. I don't think it's very good, though.
I've added an API key to the user model, which is created when the user is first authenticated:
class User
include DataMapper::Resource
property :id, Serial, :key => true
property :uid, String
property :name, String
property :nickname, String
property :created_at, DateTime
property :api_key, String, :key => true
end
....
get '/auth/:name/callback' do
auth = request.env["omniauth.auth"]
user = User.first_or_create({ :uid => auth["uid"]},
{ :uid => auth["uid"],
:nickname => auth["info"]["nickname"],
:name => auth["info"]["name"],
:api_key => SecureRandom.hex(20),
:created_at => Time.now })
session[:user_id] = user.id
session[:api_key] = user.api_key
flash[:info] = "Welcome, #{user.name}"
redirect "/success/#{user.id}/#{user.api_key}"
end
If the authorisation works then the api_key is supplied to the Android app, which will presumably store it on the device somewhere:
get '/success/:id/:api_key', :check => :valid_key? do
user = User.get(params[:id],params[:api_key])
if user.api_key == params[:api_key]
{'api_key' => user.api_key}.to_json
else
error 401
end
end
All API calls are protected as in the link in my original post:
register do
def check (name)
condition do
error 401 unless send(name) == true
end
end
end
helpers do
def valid_key?
user = User.first(:api_key => params[:api_key])
if !user.nil?
return true
end
return false
end
end
For public use I'll only allow SSL connections to the server. Any suggestions for improvement would be welcome.