I need to implement authentication scheme using Apple SSO for my application:
Check for an signed user in Settings TV Provider
Sing in into TV Provider from my app if there is no a signed user
Get authentication payload of a signed user from my provider backend with tokens, uuid, etc.
What the main steps to config and implement Apple SSO authentication using VideoSubscriberAccount framework because unfortunately there is no much info and samples about?
There are several common steps to implement authentication scheme with Apple SSO:
1. Config your provisional profile, .entitlements and Info.plist files
YourApp.entitlements file must have a special key that enables SSO for your app:
com.apple.developer.video-subscriber-single-sign-on Boolean YES
This entitlement also should be present in your provisional profile e.g.:
Info.plist must have next key with a message that will be shown to user on first access to the video subscriptions:
NSVideoSubscriberAccountUsageDescription String "This app needs access to your TV Provider."
2. Create an account manager instance and implement delegate methods to coordinate access to a subscriber's account.
import VideoSubscriberAccount
...
let accountManager = VSAccountManager()
accountManager.delegate = self
...
extension YourController : VSAccountManagerDelegate {
func accountManager(_ accountManager: VSAccountManager, present viewController: UIViewController) {
window?.rootViewController?.present(viewController, animated: true, completion: nil)
}
func accountManager(_ accountManager: VSAccountManager, dismiss viewController: UIViewController) {
viewController.dismiss(animated: true, completion: nil)
}
func accountManager(_ accountManager: VSAccountManager, shouldAuthenticateAccountProviderWithIdentifier accountProviderIdentifier: String) -> Bool {
return true
}
}
3. Determine the state of the application's access to the user's subscription information.
accountManager.checkAccessStatus(options: [VSCheckAccessOption.prompt : true]) { status, error in
...
}
If the app tries to access to subscription information first time next prompt will be shown:
4. Request information about the subscriber's account.
If access is granted you can make a metadata request to check for a signed user:
if case .granted = status {
let request = VSAccountMetadataRequest()
request.includeAccountProviderIdentifier = true
request.isInterruptionAllowed = true
accountManager.enqueue(request) { metadata, error in
...
}
}
The provider selection list and sign-in form will be shown if there is no signed account:
To skip providers list view you can set supported providers identifiers to the request e.g.:
request.supportedAccountProviderIdentifiers = ["Hulu"]
5. Second metadata request with required attributes from a identity provider's info endpoint
If there is no error and signed account is present you should call to your identity provider's info endpoint to obtain required attributes for second metadata call such as:
attributeNames: a list of SAML attributes needed
verificationToken: Base64 encoded signed authentication request from the service provider to the identity provider
channelIdentifier: service provider entity id
And make second request with these parameters:
request.attributeNames = attributeNames
request.verificationToken = verificationToken
request.channelIdentifier = channelIdentifier
accountManager.enqueue(request) { metadata, error in
...
}
6. Request translation of SAML authentication response
The seconds metadata request responds with Apple's SAML payload that should be sent to your identity provider's translation endpoint then the endpoint parses that element and returns a response that resembles the authentication payload e.g.:
if let samlPayload = metadata?.samlAttributeQueryResponse {
let body = [
...
"saml" : samlPayload
]
fetch("https://your.identity.provider/saml/translate", httpBody: body)
}
The endpoint should respond with JSON which contains all your authentication data: tokens, uuid etc.
Related
I've been working with the Amplify SDK to get federatedSignIn working with my iOS app with "Sign in with Apple" and Cognito to eventually make calls to API Gateway / Lambda functions.
TL;DR : My access token does not appear to be "automatically included in outbound requests" to my API as per the last paragraph of this section of the docs : Cognito User pool authorization
I have successfully authenticated using the tutorial found here Authentication Getting Started and other various Youtube videos on the Amazon Web Services channel.
Upon successful sign in through Apple I'm given an ASAuthorizationAppleIDCredential object. This contains the user field (token) which I pass to the Amplify.Auth class using the following Swift code :
func signIn (with userId: String)
{
guard
let plugin = try? Amplify.Auth.getPlugin(for: AWSCognitoAuthPlugin().key),
let authPlugin = plugin as? AWSCognitoAuthPlugin,
case .awsMobileClient (let client) = authPlugin.getEscapeHatch()
else
{
return
}
client.federatedSignIn(providerName: AuthProvider.signInWithApple.rawValue, token: userId) { (state, error) in
if let unwrappedError = error
{
print (unwrappedError)
}
else if let unwrappedState = state
{
print ("Successful federated sign in:", unwrappedState)
}
}
}
All appears to be successful and to double check I use the following bit of code to ensure I'm authorized :
func getCredentialsState (for userId:String)
{
let provider = ASAuthorizationAppleIDProvider()
provider.getCredentialState(forUserID: userId) { (credentialsState, error) in
if let unwrappedError = error
{
print (unwrappedError)
}
switch credentialsState
{
case .authorized:
print ("User Authorized")
case .notFound, .revoked:
print ("User Unauthenticated")
case .transferred:
print ("User Needs Transfer")
#unknown default:
print ("User Handle new use cases")
}
}
}
In the console I see "User Authorized" so everything appears to be working well.
However when I then go to make a call to Amplify.API.post I get the following error:
[Amplify] AWSMobileClient Event listener - signedOutFederatedTokensInvalid
Failed APIError: Failed to retrieve authorization token.
Caused by:
AuthError: Session expired could not fetch cognito tokens
Recovery suggestion: Invoke Auth.signIn to re-authenticate the user
My function for doing the POST is as follows :
func postTest ()
{
let message = #"{'message": "my Test"}"#
let request = RESTRequest (path: "/test", body: message.data(using: .utf8))
Amplify.API.post (request:request)
{
result in switch result
{
case .success(let data):
let str = String (decoding: data, as: UTF8.self)
print ("Success \(str)")
case .failure(let apiError):
print ("Failed", apiError)
}
}
}`
I then went into the API Gateway UI and changed the generated Method Request on my resource from AWS IAM to my Cognito User Pool Authorizer thinking this was the issue. I also changed the awsAPIPlugin authorizationType to "AMAZON_COGNITO_USER_POOLS" in my amplifyconfiguration.json file. This unfortunately did not have any affect.
I've seen posts such as this issue User is not created in Cognito User pool for users logging in with Google federated login #1937 where people discuss the problem of having to to use a web ui to bring up the social sign in. I understand that Apple will reject your app sometimes for this. Therefore this is not a solution.
I then found this post which seems to resolve the issue however this appears to use the old version of the SDK? Get JWT Token using federatedSignIn #1276
I'm not great with Swift (I'm still an Objective C expert, but am slowly learning Swift) so I'm uncertain which path to go here and whether this is actually a solution? It does seem to be quite more complicated than the function I have that does my POST? The RESTRequest does seem to be a simple and easy solution but I'm uncertain how to pass it the Authorization token (or even how to get the token if it is needed here).
However, everything I've read about the SDK is that the authorization should be handled automatically in the background according the docs in my first link above. Specifically pointed out, again, here : Cognito User pool authorization. The last paragraph here states 👍
With this configuration, your access token will automatically be included in outbound requests to your API, as an Authorization header.
Therefore, what am I missing here as this does not appear to automatically include my access token to my outbound requests to my API?
I need help verifying the OTP with Firebase.
I Managed to receive a SMS with the OTP but when I verify it I get automatically signed up and I only know if the OTP was valid if I signed up - else I get a popup like "invalid otp".
How can I manually validate the otp? My goal is to open another screen where the user puts in more information.
func verifyCode(){
let credential = PhoneAuthProvider.provider().credential(withVerificationID: self.CODE, verificationCode: code)
print(credential)
loading = true
//here i just want to verify my OTP without signing in...
Auth.auth().signIn(with: credential) { (result, err) in //here i am signing in...
self.loading = false
if let error = err{
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(.error)
self.code = ""
self.errorMsg = error.localizedDescription
withAnimation{ self.error.toggle()}
return
}
self.gotoRegistration = true
withAnimation{self.status = true}
}
}
There is no way to use Firebase Authentication's phone/OTP provider without automatically signing the user in.
But the fact that the user is signed in to Firebase, does not mean that you have to grant them access to all parts/data in your app. If you want them to provide more information, you can do so before or after signing them in to Firebase, and make it part of the same sign-up flow as far as the user is concerned.
So something like:
// Sign the user in with Firebase
// Check if the user has provider the additional registration information
// If not, send them to the registration information screen
// If so, send them to the next screen of the app
You can also enforce these rules in your back-end code, or (if you use one of Firebase's back-end services) in the server-side security rules.
I'm trying to implement Sign in with Apple since it's required now when you have other third party login library. My question is, is there a way for me to get apple's auth token just like from facebook and google login? All I could get is identity token and authorization code from ASAuthorizationAppleIDCredential. I am not quite sure what is used to get user information using our API.
Thank you so much for the answer.
in ASAuthorizationControllerDelegate
get the token
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
if let data = appleIDCredential.identityToken {
let token = String(decoding: data, as: UTF8.self)
// here send token to server
}
}
}
}
send the token to the server, which must be set up
https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api
and based on this token your server will get the user information from Apple the same way you can get the facebook user data.
Then your api should implement user authentication based on this data (e.g. create user account based on the user email and return your access token to the app)
I'm having a problem integrating Firebase with Microsoft Auth in my iOS App.
The login page has been launched and I can sign in by Office365 account but login auth can not be finished because of the below Error :
"AADSTS50011: The reply url specified in the request does not match the reply urls configured for the application:[app-id]"
I did check the setting in Firebase and below are the settings I add in the app of Azure Active Directory :
Web redirect URL : "*.firebaseapp.com/__/auth/handler"
Supported account types : "Accounts in any organizational directory (Any Azure AD directory Multitenant)"
Here are the swift code I implement :
provider = OAuthProvider(providerID: "microsoft.com")
provider?.customParameters = ["prompt": "consent",
"login_hint": "Login Hint"]
provider?.scopes = ["mail.read", "calendars.read"]
provider?.getCredentialWith(_: nil){ (credential, error) in
if let credential = credential {
Auth.auth().signIn(with: credential) { (authResult, error) in
if let error = error {
print(error.localizedDescription)
}
}
}
}
Does anyone know how to solve the problem or have the same problem?
When registering apps with these providers, be sure to register the
*.firebaseapp.com domain for your project as the redirect domain for your app.
Have you replaced * with your projectName? You can find your Authorized Domain under Authorized Domains in firebase console. The redirect uri should be something like
https://yourFirebaseApp.firebaseapp.com/__/auth/handler
I'm trying to setup Google Sign in for both my Android and iOS app.
The Google login is configured in the apps and should send the id token retrieved in the app to the server. The PHP code on serverside should then use the Google Client to verify the Id token that is sent using the following code:
$client = new Google_Client(['client_id' => $CLIENT_ID]);
try{
$payload = $client->verifyIdToken($request["idToken"]);
} catch(Exception $e){
api_error("ID verification failed E1; ".$e->getMessage(), 304);
}
if($payload) {
$sid = "G".$payload['sub'];
} else {
api_error("ID verification failed E2;", 304);
}
However, while this code verifies the idtoken sent by me Android app successfully it fails when sending the idtoken from the iOS app. The payload variable returns null and always reaches the ID verification failed E2 part of the code.
Debugging the idtoken that is sent from the mobile devices in the REST api endpoint https://www.googleapis.com/oauth2/v3/tokeninfo?id_token= gives me a successfull response for both Android and iOS. The only difference I can discover in the response is the azp and aud values. Given the apps use different client ids I assume this difference is as expected.
On the Android code the Google SDK has the requestIdToken(webClientId) method which allows you to set the web client id variable in order to request the id token. For the iOS Google Sign In I cannot find the option to set the web client id. I tried to set the webClientId in as the serverClientId in the GIDSignIn.sharedInstance() but this gives me a 400 error telling me the audience is invalid.
Is there a way to set this webclientid and is there a requestIdToken equivalent for iOS? I'm currently using the following code to obtain the idtoken on iOS:
#IBOutlet weak var googLoginButton: GIDSignInButton!
#IBOutlet weak var fbLoginButton: FBSDKLoginButton!
let tokenValidation = TokenValidation();
var isShown = false;
override func viewDidLoad() {
super.viewDidLoad()
fbLoginButton.delegate = self;
GIDSignIn.sharedInstance().delegate = self
GIDSignIn.sharedInstance().uiDelegate = self
}
The TokenValidation class is a custom class that I wrote to send the data to the server.
The ViewController has a GIDSignInButton that calls the following function:
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!,
withError error: Error!) {
if (error == nil) {
let idToken = user.authentication.idToken
tokenValidation.setFBAccessToken(accessToken: nil);
tokenValidation.setGoogleIdToken(googleIdToken: idToken);
startTokenValidate(tokenValidation: tokenValidation);
} else {
showMessage(messageStr: error.localizedDescription);
}
}
The startTokenValidate function then calls my API endpoint to verify the token. The error variable inside the app is always nil.
Is there any way to retrieve a correct idtoken or are there any alternatives to validate the Google Sign In with the backend server?
In my case, it is because the client ID in my .env file does not match the aud value returned by the Google API when iOS token is sent. The client ID set in my .env only matches with the aud returned for Android token. I don't know the best practice for this. Maybe you need to have different API client for different platform, which is instantiated with different client ID. But my simple solution is, I just omit any argument while creating the client, like this:
$client = new \Google_Client();
Looking at the source code of Google API client, when the client ID is not set, it will skip checking the aud value. Again, there might be better and more secure way to do this but this is what I did with mine.