Firebase "admin user" in iOS? - ios

Here's what I want to achieve:
I have an iOS frontend, a Vapor backend, and a Firebase storage bucket. I have an app where a small portion of the functionality is being able to add images to some Note objects. When the user attaches an image to a Note, I want to upload the image to the bucket, get the key/url where it's stored, and store that in the backend DB. All that is simple enough, except - I want the bucket to be accessible ONLY by my iOS code and my backend code, so not just publicly accessible by anyone.
The backend is covered via the service account stuff. but I'm not sure about how to cover the iOS side. Firebase generated this basic rule to prevent public access:
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if request.auth != null;
}
}
}
I believe this rule allows only Firebase-authenticated users to use the bucket, but I do not plan to authenticate my users through Firebase. Is there a way I can set up rules such that the bucket is only accessible via my own iOS code? Should I essentially just create a single Firebase user, and have the app authenticate as that user on every app launch? Can I configure Firebase such that it uses some secret that allows it to perform storage operations that get past the rules?
**edited for clarity

As #jnpdx commented, have a look at Firebase's anonymous authentication, which allows you to sign in (and identify) users without asking for credentials. Based on that you'll then want to ensure that each user can only read the files they're authorized for.
While if request.auth != null may seem good for that initially, I find it's often better to start with the principle of least privilege - and for example have some form of content ownership based access rules.
Finally, also have a look at Firebase App Check which was introduced earlier this year, and works together with the attestation providers on your phone to reduce the risk of unauthorized code being used to call the Storage APIs.

Related

Is there any way to use the same anonymous authentification for Firebase in main App and extension?

My app is using Firebase authentification to store data in the firebase realtime database. This should be done by the main app and by my extension. The problem is that it seems to create two different users, one for the main app, and one for the extension. I just login like this:
Auth.auth().signInAnonymously() { (user, error) in
MyVariables.uid = user!.uid
}
Is there any way to share the user? I use the same plist for the main app and extension (via appgroup). It also works to write into the database - just with different user IDs.
You can get the token of the anonymous user with FIRUser.getIDTokenWithCompletion:. The find a secure way to pass that from the app to the extension (or vice versa) and pass it to FIRAuth.signInWithCustomToken:completion:. Note that I haven't tried this myself (hence the lack of actual code), but it should work.
Update: Bad news. I checked with one of the our Auth engineers and it turns out that there's no API that allows passing a token to an extension that you can then use there to sign in. Sorry about that.

When can ADAL tokens be shared? (iOS)

In my application, I am getting an access token via ADAL's acquireTokenSilent() for one resource, which succeeds, and then I try to get an access token for another resource and it says it was not found, and hence I have to call the API to explicitly prompt for credentials. This is a problem since then the user has to login twice with the same credentials in order to access two different resources.
I am using the same authority for each resource. Here is the message that shows there is no hit in the cache for the second resource.
May 4 13:22:37 iPad MyApp[290] : ADAL 2.4.1 iOS 10.2.1 [2017-05-04 20:22:37 - XXXX] INFO: No items were found for query: (resource https://MYRESOURCE + client + authority https://login.windows.net/common)
So my question is, under what circumstances will tokens be shared across resources, and is there any special allowances (ways to use the APIs) which allow this?
If you are building two native clients (public clients) and you want to enable single sign on across the two, one option is to share the App ID between the apps versus passing the actual token from one service to another service.
For example lets say your company name is Contoso. You have a Calendar Mobile App, and a Document Editor App.
You can create a single Native Client Application with:
A common application name, like "Contoso Apps"
Redirect URIs for both apps
Permissions required for the sum of the two applications
Then when a user signs into either application, they will see a login screen with the generic name "Contoso Apps", and prompted to consent to permissions for both apps at the same time. Now this might be a little bit of a bad experience, since the permissions of the two will probably be more than the individual permissions required, but that could be fixed in the future with Incremental Consent.
Then assuming you are using our authentication libraries which automatically caches the access tokens, when the user opens the second application, they will not be prompted to consent because you already have a token cached for that Application ID.
This obviously is not the best solution, but one that has been used in the past for large enterprise applications.

AWS web and mobile hub application

On AWS, I wish to create an application that allows a user to sign in via mobile, web or both.
I created a system using API Gateway, Lambdas and DynamoDB for the back end. I have sign in working for web using JavaScript but was having issues finding a Swift example for iOS of the same code (objective C only available). So I created a mobile hub application, imported my existing API and have a working iOS sign in.
The issue is that the iOS side uses the Mobile Hub so I now have 2 different User Pools so you can't sign up on web and log in on mobile (or vice versa).
I tried to change the settings in the iOS app to point to my web app Cognito User Pool settings and remove the secret but it errors as it can not be null or empty.
Why does the mobile hub require a client secret? The JavaScript documentation suggested was bad practice since code can be de-compiled and the secret extracted.
There doesn't seem to be any consistent documentation that explains, what I would guess, as the most common use case of a mobile and web app!
The other issue is that I can download my API client SDK for my API Gateway for use with web app and iOS app. But, the generated mobile hub app includes a REST based call? Am I going crazy here or does the official web approach not link to the official mobile approach?
So the key questions are:
Can or should I change the mobile apps to point at the original
Cognito?
if so should I remove the client secret?
can I effectively ignore the Mobile Hub after set up and use it purely for code generation? Assuming it was then working can I just use the generated client SDK for my API Gateway?
Is there a better way of setting up an iOS (later Android too) and web app?
I've spent considerable time and effort, and tried many approaches.
The "Mobile Hub" nicely sets up the user pool, identity pool, IAM roles etc.
The keys etc are mostly provided in the Info.plist file, although ( unwisely ) the developers of the user pool AWSSignInProvider made it have hardcoded keys in a configuration file.
SO:
If you don't intend to use the "Mobile Hub" console application for making changes to your mobile app configuration, then you won't need any more downloads. In that case, you don't have to worry about changes to Info.plist or the configuration file, and you can edit what you want.
It is unclear if you are going to use the mobile hub created identity pool and just want to insert your user pool, or if you want to change both pools. Obviously if you are using the same identity pool then some of the changes below will not be needed (they will be obvious because you will be changing them to be exactly the same).
So all you have to do is change the ID's to get everything hooked up correctly.
In general you need to fix all the downloaded keys and ID's in Info.plist and the configuration file, and then you need to update the server configuration. In more detail, here are the places you need to change it:
in the app:
update all the keys in Info.plist to be the keys you want. (specifically credentials provider and identity manager keys for google) But if you are using other mobile hub services, check the keys there too.
If you are using s3, and some of the other services the directory names/database names are also stored in the code... I leave it as an activity to find them.
in the file MySampleApp->AmazonAWS->AWSConfiguration.swift edit the keys provided by Mobile hub to match your user pool (do this while quietly swearing under your breath because they are not in Info.plist)
in the console:
put your app name in your user pool APP's list if it is not there, and record the user pool id, app id, and app secret.
click on federated identities and the identity pool created by mobile hub and update the authentication provider to use your cognito user pool id and app id.
if you are changing the identity pool too, then you will need to look at the IAM Roles for your auth and unauth users and specifically edit the policy that is named: .....yourapp....signin_MOBILEHUB_xxxxxxx, and change the identity pool id in that policy to be the one you want to use. Do this for both auth and unauth.
( you can change the id if you only use one pool, or add another to a list of id's like this if you will have multiple identity pools (for test...etc))
"Resource": [
"arn:aws:cognito-identity:*:*:identityPool/us-east-1:8s8df8f8-sd9fosd9f0sdf-999sd99fd",
"arn:aws:cognito-identity:*:*:identityPool/us-east-1:dfsf9099-sd9fosd9f0sdf-sd9f0sdf09f9s"
]
similarly, in the trust relationship associated with the roles, you need to fix the id's, (or handle multiple ID's if you want the role to serve multiple identity pools). Here is how to specify multiple id's there.
"Condition": {
"ForAllValues:StringLike": {
"cognito-identity.amazonaws.com:aud": [
"us-east-1:8s8df8f8-sd9fosd9f0sdf-999sd99fsdfdd",
"us-east-1:dfsf9099-sd9fosd9f0sdf-sd9f0sdf09f9s"
]
},
if you are using google too... you need to make sure that you have an identityProvider in the IAM configuration for google (mobile hub did that for you) and if you are using your own identity pool , in your federated identity pool authorization providers configuration you will need to select google open id provider (and put google in the authorized providers too (but I don't think this part is strictly needed))
facebook doesnt use OpenID Connect, it has a proprietary way of configuring into the authentication providers section, so enter those keys if needed in the identity pool authentication providers section.
And that should be enough to make it work.
And no you are not going crazy... the documentation does not match the current IOS SDK. Mobile hub uses the aws-mobilehub-helper-ios (github) which is built on TOP of the sdk, so the documentation does not apply to that either! Mobile Hub Helper has a nice design, so I recommend you use it, rather than the raw SDK.
(and lastly... and I am out of my depth here because I don't use API gateway, but my understanding was that the API Gateway is a way to get credentials to use AWS Services, and with the mobile hub app you will be using Cognito to get those credentials, so I am not sure you will need to bring the API Gateway into it...at all)
UPDATE
You may want to use no client secret for users of your javascript app, and use a IOS Mobile App on the same pool too. This can be done in two ways:
1) The better way is that you create two different client's in the user pool. For one you will generate a client secret, for the other you will UNCHECK the "generate client secret" box.
Then in your Federated Identity Pool you go to Authentication Providers, and click on Cognito, and specify TWO DIFFERENT PROVIDERS USING THE SAME USER POOL ID. (This is not really two different providers, but that is how the console makes you specify it). And you put the two different client ids in those providers.
Now both the IOS app and the Javascript app can access the pool and get authentication and credentials from the identityProvider and credentialsProvider.
2) A not so good way. The reason this way is worse is because I don't know the impact (if any) it has on the security of your mobile app. And at AWS there is nobody to ask the question to without buying a support contract. But the other way exists.
What you do is use the same client id in both apps, and you don't generate a client secret. To do this you put "nil" in the clientSecret. This works fine with some caveats.
First, the AWS Mobile Hub has a bug in it's AWSCognitoUserPoolsSignInProvider. That class requires that the clientSecret is non-null. But in the SDK, the only way to tell the SDK that you want no client secret is to pass nil! However there are workarounds.
(What I did was use the AWSCUPIdPSignInProvider.swift (that I wrote), which will work fine and I have a version that will take a nil for the secret. I did that because it was faster for me to test this out. you can find that signin provider on github if you want to use it)
But a better (more future proof) solution is probably to use the AWSCognitoUserPoolsSignInProvider that the mobile hub delivers, but change the code in AWSMobileClient to configure and register your own pool rather than letting AWSCognitoUserPoolsSignInProvider do it for you.
I haven't bothered to try this, (because we only have to do it because AWS has not gotten around to updating the github aws-mobilehub-helper-ios yet). But basically in AWSMobileClient instead of this code:
func setupUserPool() {
// register your user pool configuration
AWSCognitoUserPoolsSignInProvider.setupUserPoolWithId(AWSCognitoUserPoolId, cognitoIdentityUserPoolAppClientId: AWSCognitoUserPoolAppClientId, cognitoIdentityUserPoolAppClientSecret: AWSCognitoUserPoolClientSecret, region: AWSCognitoUserPoolRegion)
AWSSignInProviderFactory.sharedInstance().registerAWSSignInProvider(AWSCognitoUserPoolsSignInProvider.sharedInstance(), forKey:AWSCognitoUserPoolsSignInProviderKey)
}
you would have something like this code
func setupUserPool() {
// register your user pool configuration
// find the service configuration (we don't know if they set it as default)
let credentialProvider = AWSCognitoCredentialsProvider(regionType: .USEast1 (or your region), identityPoolId: "YourIdentityPoolId")
let configuration = AWSServiceConfiguration(region: .USWest2 (or your region), credentialsProvider: credentialProvider)
// configure and put your own user pool in the service configuration
let userPoolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: AWSCognitoUserPoolAppClientId, clientSecret: nil, poolId:AWSCognitoUserPoolId)
// now we register that pool with the service configuration using the key they use
AWSCognitoIdentityUserPool.register(with: configuration, userPoolConfiguration: userPoolConfiguration, forKey: AWSCognitoUserPoolsSignInProviderKey)
AWSSignInProviderFactory.sharedInstance().registerAWSSignInProvider(AWSCognitoUserPoolsSignInProvider.sharedInstance(), forKey:AWSCognitoUserPoolsSignInProviderKey)
}
But as I said above, solution 1, use two different clients and specify two different providers is preferred.

iOS safely pass content between apps

I'm curious if there is a way to safely pass content between apps on iOS. The ultimate goal is to implement oauth between two ios apps.
Since apps are not guaranteed to have unique url schemes, this option is out.
I have considered using keychain groups, but do not have experience with this. It looks like an app needs to specify exactly which apps can access the keychain items.
Are there any other options? Is there some sort of identifier (such as android bundle ID) that can be used to verify the apps during a request?
You can use URL schemes for this.
The basic process
You'll have a ServerApp and many ClientApps. The ServerApp listens to an URL-scheme like serverapp://. The client then can make a call to the server to ask it for authentication. The client has to implement an URL-scheme too. E.g. ClientAppOne implements the URL scheme clientapp1://. The server takes as parameter a backlink to the client app. E.g. the client calls the URL serverapp://auth?back=clientapp1%3A%2F%2Fserverapp-auth (here the backlink is clientapp1://serverapp-auth and has been urlencoded).
The server then checks the users identity, asks him for permission, password, etc. and then uses the backlink to provide the data. How the backlink works exactly is application specific, but you usually need at least 2 parts: an access token and a username. E.g. a backlink will then be clientapp1://serverapp-auth?success=1&token=fi83ia8wfzi3s8fi8s3f8si8sf&user=robert or maybe in case of error clientapp1://serverapp-auth?success=0&errno=421. The client then needs to verify the accesstoken through some public (or private) API, e.g. https://serverapp.example.com/userdetails?apikey=fai83jw93fj93389j&token=fi83ia8wfzi3s8fi8s3f8si8sf. The server will return some structured response.
Necessary components
an URL scheme on the server App
an URL scheme on each client App
an SDK that is to be included into each client app and that handels the details of authentication, and a standard UI component (e.g. facebook has a standard button that says "login with facebook", so the ServerApp needs some re-recognizable button that says something like "login with ServerApp")
a server that provides services that can be accessed through the access token.
a defined API that explains how the client has to communicate with the server
an SDK to be included into the client that handels such client-server-communication (should be part of the SDK mentioned in component 3.)
maybe a wiki that documents all of the steps above, so that you and other developers dont lose track
a way to invalidate access tokens, and a way for the client to detect if an access token has been invalidated. furthermore, if the user changes his password, all access tokens should be invalidated.
Random notes
in your client app you can check if the serverapp is installed by calling [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:#"serverapp://auth"]].
the URL schemes should be sufficently collission-free. These URLs are never seen by users, only by developers, so they don't have to be beautiful. You can e.g. append the iTunes-Connect-App-ID to your URL-scheme, like serverapp1234567://. This will greatly reduce the possibility that someday some other app will use the same URL scheme.

Getting Server Rejected error when upload using Google Picker, Oauth2

We are trying to use google.picker to have our users upload files to our drive account (i.e., the user is not required to have a Google account to upload).
We're trying to use regular Google accounts as application-owned accounts and got our AUTH_TOKEN using OAuth2 and set it using .setOAuthToken(AUTH_TOKEN) . We followed everything described in the docs.
However, when uploading, we got a Server Rejected error. The call to https://docs.google.com/upload/resumableupload?authuser=undefined returned:
{"errorMessage":{"reason":"REQUEST_REJECTED","additionalInfo":{"uploader_service.GoogleRupioAdditionalInfo":{"completionInfo":{"status":"REJECTED"},"requestRejectedInfo":{"reasonDescription":"agent_rejected"}}},"upload_id":"AEnB2Ur64Gb0JDCk_8mg5EhpdcaqL82wBQHumHjcGvDqYibtksmUzhfhBolsmBFzRuvQPRyi43SYfactJZvIWYrQ6xAqzu3L9g"}}
We know we cannot use service accounts since the picker doesn't support it.
Do we miss something in getting the AUTH_TOKEN? Do we need to something in the console?
Give us a little more code, or check the call to gapi.auth.authorize()
Check that you are using the correct scope to obtain the OAuth token.
Scope should be https://www.googleapis.com/auth/drive
Double-Check the scope declaration:
https://developers.google.com/accounts/docs/OAuth2Login#sendauthrequest
Check the call to gapi.auth.authorize()
window.gapi.auth.authorize(
{
'client_id': clientId,
'scope': scope,
'immediate': false
},
handleAuthResult);
from: https://developers.google.com/picker/docs/#hiworld
Without an actual code sample, it is very difficult to say exactly what is going on. Most likely it is the auth token colection. However, it may also be something as simple as not defining a google User (clientID) which in turn impacts the gapi.auth.authorize() call.
maybe this thread can help you: https://groups.google.com/forum/#!topic/Google-Picker-API/PPd0GEESO78
It is about setting the oauth context
or this one:
https://productforums.google.com/forum/#!msg/drive/GDl4uBkkbxM/jRejcxI-EV8J
It is about the type of file you try to upload with autoconvert on..
Use a Google Apps script on Drive with the function doPost to send data to the server. Then write to file with the Drive API. On publish, you have to set the permissions to "accessible to anyone, even anonomous" if doing cross-domain calls. Make the script run under your user name in Google (for testing), but most likely you would want that function moved onto some application-user account in Gmail.
If you need a level of authentication involved, even if the script is made public, you may authenticate against a CloudSQL hosted database and/or with the Jdbc library to connect to an external resource.
The Scope seems to be the problem.OAuth Token must be obtained using correct scope only:
http://tinyurl.com/ldotq4y
Easily replace scope: 'https://www.googleapis.com/auth/drive.readonly' to scope: 'https://www.googleapis.com/auth/drive' . So that you're allow to make change including upload something to your Google Drive account.

Resources