## Customization ### Customize Object Key Path You can customize your key path by defining a prefix resolver: ```swift import Amplify import AmplifyPlugins // Define your own prefix resolver, that conforms to `AWSS3StoragePluginPrefixResolver` struct MyDeveloperDefinedPrefixResolver: AWSS3PluginPrefixResolver { // This function is called on every Storage API to modify the prefix of the request. func resolvePrefix( for accessLevel: StorageAccessLevel, targetIdentityId: String?, completion: @escaping (Result) -> Void ) { // Use "myPublicPrefix" for guest access levels if accessLevel == .guest { completion(.success("myPublicPrefix/")) return } // Use "myProtectedPrefix/{targetIdentityId}" and "myPrivatePrefix/{targetIdentityId}" respectively // `targetIdentityId` is the value passed into the Storage request object, if let identityId = targetIdentityId { if accessLevel == .protected { completion(.success("myProtectedPrefix/" + identityId + "/")) } else if accessLevel == .private { completion(.success("myPrivatePrefix/" + identityId + "/")) } return } // Use "myProtectedPrefix/{identityId}" and "myPrivatePrefix/{identityId}" respectively // `identityId` is the identity id of the current user getIdentityId { result in switch result { case .failure(let error): completion(.failure(error)) case .success(let identityId): if accessLevel == .protected { completion(.success("myProtectedPrefix/" + identityId + "/")) } else if accessLevel == .private { completion(.success("myPrivatePrefix/" + identityId + "/")) } } } } public func getIdentityId(completion: @escaping (Result) -> Void) { Amplify.Auth.fetchAuthSession { result in do { let session = try result.get() if let identityProvider = session as? AuthCognitoIdentityProvider { let identityId = try identityProvider.getIdentityId().get() completion(.success(identityId)) } } catch { completion(.failure(StorageError.authError("Failed to retrieve auth session", "", error))) } } } } ``` Then configure the storage plugin with the resolver. ```swift let storagePlugin = AWSS3StoragePlugin(configuration: .prefixResolver(MyDeveloperDefinedPrefixResolver())) Amplify.add(storagePlugin) ``` Add the IAM policy that corresponds with the prefixes defined above to enable read, write and delete operation for all the objects under path *myPublicPrefix/*: ```xml "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject" ], "Resource": [ "arn:aws:s3:::your-s3-bucket/myPublicPrefix/*", ] } ] ``` If you want to have custom *private* path prefix like *myPrivatePrefix/*, you need to add it into your IAM policy: ```xml "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject" ], "Resource": [ "arn:aws:s3:::your-s3-bucket/myPrivatePrefix/${cognito-identity.amazonaws.com:sub}/*" ] } ] ``` This ensures only the authenticated users has the access to the objects under the path. #### Passthrough PrefixResolver If you would like no prefix resolution logic, such as performing S3 requests at the root of the bucket, create a prefix resolver that returns an empty string: ```swift public func resolvePrefix( for accessLevel: StorageAccessLevel, targetIdentityId: String? ) -> Result { return .success("") } ``` #### Client validation You can also perform validation based on the access controls you have defined. For example, if you have defined Guests with no access then you can fail the request early by checking if the user is not signed in: This will only stop your app from making a call with an unauthenticated user. You must also set up your IAM policies to protect your resources. See [CLI Storage](/cli/storage/import) for more details. ```swift struct MyDeveloperDefinedPrefixResolver: AWSS3PluginPrefixResolver { public func resolvePrefix( for accessLevel: StorageAccessLevel, targetIdentityId: String?, completion: @escaping (Result) -> Void ) { guard Amplify.Auth.getCurrentUser() != nil else { completion(.failure(.authError("User is not signed in", "", nil))) return } // Continue to resolve the prefix for the request // ... } } ```