aws-amplify / amplify-swift

A declarative library for application development using cloud services.
Apache License 2.0
442 stars 192 forks source link

use Cognito UserPool only (without IdentityPool) #1040

Closed tomanwalker closed 3 years ago

tomanwalker commented 3 years ago

Possible for JS sdk.

For our use-case - we have REST API protected by Cognito User Pool. So we only want Mobile to get access token (contains user's email) and use it to communicate with the aforementioned REST API. Documentation / testing shows that IdentityPool is currently mandatory even if it is not needed.

https://docs.amplify.aws/lib/auth/existing-resources/q/platform/ios

palpatim commented 3 years ago

Hi @AlexShkunov.

I believe Amplify API should be able to handle UserPool-only authorization, but it might require some custom authentication interceptors to inject the tokens properly.

@lawmicha, are you aware of the current state of this?

Documentation / testing shows that IdentityPool is currently mandatory even if it is not needed

By default, the Amplify CLI creates an Identity Pool that your User Pool can federate into. This makes it easier to get credentials to access other AWS services, since only a few (e.g., API Gateway, AWSAppsync) can authorize via User Pools. Even if the Identity Pool (and appropriate IAM roles) are created, it should still be possible to access your API Gateway-backed REST endpoint with user pool tokens.

Can you provide some more details around how you configured your REST API and how you tested?

tomanwalker commented 3 years ago

@palpatim Thanks for the quick reply. The link you provided also shows both User and Identity pools.

By default, the Amplify CLI creates an Identity Pool that your User Pool can federate into

We use Existing (Created straight in AWS web console)

This makes it easier to get credentials to access other AWS services

Dont need this - Mobile app should not have access to AWS resources straight away

Can you provide some more details around how you configured your REST API and how you tested?

here is NodeJS we've built for testing iOS details are below that

// ### Server side (Backend)
var express = require('express');
var router = express.Router();
var CognitoExpress = require('cognito-express');

var cognitoExpress = new CognitoExpress({
    region: 'eu-west-1',
    cognitoUserPoolId: '123',
    tokenUse: "access", //Possible Values: access | id
    tokenExpiration: 3600000 //Up to default expiration of 1 hour (3600000 ms)
});

router.use('/api', function(req, res, next){

    var authToken = req.get('authorization') || 'none';
    if( !authToken.includes('Bearer') ){
        //Fail if token not present in header.
        return next({ status: 401, message: "Access Token missing from header" });
    }

    var accessToken = authToken.split(' ')[1];
    cognitoExpress.validate(accessToken, function(err, response){

        if(err){
            return next({ status: 401, message: "Unauthorized" });
        }

        // Fine. Proceed.
        res.bag.user = response.username;
        res.bag.sub = response.sub;
        return next();
    });
});

// ### Client side (integration testing)
var Amplify = require('@aws-amplify/core').Amplify;
var Auth = require('@aws-amplify/auth').Auth;

// client_id - is a Cognito App Client generated WITHOUT secret
var amplifyOpts = {
    "aws_cognito_region": 'eu-west-1',
    "aws_user_pools_id": '123',
    "aws_user_pools_web_client_id": '123',
    "authenticationFlowType": 'USER_SRP_AUTH', // USER_SRP_AUTH | USER_PASSWORD_AUTH
    "oauth": {}
};

Amplify.configure(amplifyOpts);

Auth.signIn(user, pass).then(function(result){

    var accessToken = result.signInUserSession.accessToken.jwtToken;
    var headerToPass = 'Bearer ' + accessToken;

    // <headerToPass> can now be used for REST API requests
        var headers = {
           'authorization': headerToPass,
           'accept': 'application/json'
        };

}).catch(function(err){
    console.log('getAmplifyToken - catch - err = %j', err);
});

For iOS - following methods been considered: Amplify.Auth.signIn(username: username, password: password); Amplify.Auth.fetchAuthSession

NodeJS SDK - returns AccessToken right at "SingIn" - however for iOS additional method has to be called to get

to recap 1) we dont need Mobile app to access AWS services directly 2) we only need and methods to login / refresh Cognito UserPool tokens 3) we did however created IdentityPool as a work around 4) Amplify JS SDK able to work without IdentityPool just fine

lawmicha commented 3 years ago

Hi @AlexShkunov,

Since you already have the JS client code running against your API Gateway backed by the lambda code, I'm assuming you have the correct Authorizer set up, the Method Request has the correct Authorizator selected, and the expected OAuth scopes configured.

On the iOS side, you should configure your API to use the AMAZON_COGNITO_USER_POOLS authorization type. This will set up an request interceptor internally to add the access token to your requests when you perform any API calls, (Amplify.API.get, Amplify.API.post, etc).

amplifyconfiguration.json

"awsAPIPlugin": {
                "api206480ec": {
                    "endpointType": "REST",
                    "endpoint": "https://123.execute-api.us-west-2.amazonaws.com/dev",
                    "region": "us-west-2",
                    "authorizationType": "AMAZON_COGNITO_USER_POOLS"
                }
            }

After you've signed in, Amplify.Auth.signIn, you do not need to retrieve the tokens from the session yourself like the JS sample and Ampify.API will do that automatically on each request. If you want to get the token, you can do this:

Amplify.Auth.fetchAuthSession { result in
                    do {
                        let session = try result.get()

                        // Get cognito user pool token
                        if let cognitoTokenProvider = session as? AuthCognitoTokensProvider {
                            let tokens = try cognitoTokenProvider.getCognitoTokens().get()
                            print("Access token - \(tokens.accessToken) ")
                            print("Id token - \(tokens.idToken) ")
                        }

                    } catch {
                        print("Fetch auth session failed with error - \(error)")
                    }
                }

ref: https://docs.amplify.aws/lib/auth/access_credentials/q/platform/ios

Please let us know if this works for your use case, I documented my steps to debug this over in https://github.com/lawmicha/apiRestCUP

tomanwalker commented 3 years ago

Thanks for the quick update.

Going through docs carefully, I really like the line

With this configuration, your access token will automatically be included in outbound requests to your API, as an Authorization https://docs.amplify.aws/lib/restapi/authz/q/platform/ios#cognito-user-pool-authorization

To be on a safe side, to confirm

1) we not really using AWS API Gateway - NodeJS server app is exposed via ELB/Route53 on lets say "myapp.com" does that still work? the region can be set randomly then?

{
    "awsAPIPlugin": {
        "mybackendapp": {
            "endpointType": "REST",
            "endpoint": "https://myapp.com",
            "region": "us-west-1", // any random?
            "authorizationType": "AMAZON_COGNITO_USER_POOLS"
        }
    }
}

That then removes a Need to access creds (Amplify.Auth.fetchAuthSession) - which is nice and handly

2) We still need to create IdentityPool with zero permissions, eventhough we dont Intend to access any service from Mobile?

lawmicha commented 3 years ago

Hi @AlexShkunov,

  1. the usage of AWSAPIPlugin with the REST endpointType was mainly tested against an APIGateway endpoint, however I don't see why it wouldn't work against your own service as long as your service is fine with the additonal header values that are added. You should be able to omit the region since it is only used for IAM auth. Were you able to successfully make requests using the APIPlugin with this set up?

  2. I did not get a chance to investigate this yet but it may be true that identity pool configuration is required at the time of Amplify.Auth's AWSCognitoAuthPlugin instanatiation, this limitation we can investigate further to see the exact reason why it's a requirement. (related https://github.com/aws-amplify/docs/issues/2041)

lawmicha commented 3 years ago

I investigated the usage of AWSCognitoAuthPlugin with only Cognito User Pool configuration in amplifyconfiguration.json

{
    "UserAgent": "aws-amplify-cli/2.0",
    "Version": "1.0",
    "auth": {
        "plugins": {
            "awsCognitoAuthPlugin": {
                "UserAgent": "aws-amplify/cli",
                "Version": "0.1.0",
                "IdentityManager": {
                    "Default": {}
                },
                "CognitoUserPool": {
                    "Default": {
                        "PoolId": "us-west-2_xxxxxxxxxx",
                        "AppClientId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
                        "AppClientSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
                        "Region": "us-west-2"
                    }
                },
                "Auth": {
                    "Default": {
                        "authenticationFlowType": "USER_SRP_AUTH"
                    }
                }
            }
        }
    }
}

I do see warnings about missing Cognito Identity Pool, however the app continues to function.

[Amplify] Could not read Cognito identity pool information from the configuration.

i'm able to sign in and retrieve the tokens:

// https://docs.amplify.aws/lib/auth/access_credentials/q/platform/ios
func fetchCurrentAuthSession() {
    Amplify.Auth.fetchAuthSession { result in
        do {
            let session = try result.get()
            // Get cognito user pool token
            if let cognitoTokenProvider = session as? AuthCognitoTokensProvider {
                let tokens = try cognitoTokenProvider.getCognitoTokens().get()
                print("Id token - \(tokens.idToken) ")
            }

        } catch {
            print("Fetch auth session failed with error - \(error)")
        }
    }
}
// https://docs.amplify.aws/lib/auth/signin/q/platform/ios
func signIn(username: String, password: String) {
    Amplify.Auth.signIn(username: username, password: password) { result in
        switch result {
        case .success:
            print("Sign in succeeded")
        case .failure(let error):
            print("Sign in failed \(error)")
        }
    }
}

We can take an action item to drop the warning to info level logging here https://github.com/aws-amplify/amplify-ios/blob/main/AmplifyPlugins/Auth/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin+Configure.swift#L83

and mention in the "Use Existing AWS resources" to call out that CredentialsProvider section is optional: https://docs.amplify.aws/lib/auth/existing-resources/q/platform/ios

Let us know if you see any problems using Auth without Cognito Identity Pool

github-actions[bot] commented 3 years ago

This issue is being automatically closed due to inactivity. If you believe it was closed by mistake, provide an update and re-open it.