Use Case 1. Registering a user with the application. One needs to create a CognitoUserPool object by providing a UserPoolId and a ClientId and signing up by using a username, password, attribute list, and validation data.
import'package:amazon_cognito_identity_dart_2/cognito.dart';
final userPool = CognitoUserPool(
'ap-southeast-1_xxxxxxxxx',
'xxxxxxxxxxxxxxxxxxxxxxxxxx',
);
final userAttributes = [
AttributeArg(name: 'first_name', value: 'Jimmy'),
AttributeArg(name: 'last_name', value: 'Wong'),
];
var data;
try {
data = await userPool.signUp(
'email@inspire.my',
'Password001',
userAttributes: userAttributes,
);
} catch (e) {
print(e);
}
Use case 2. Confirming a registered, unauthenticated user using a confirmation code received via SMS/email.
Use case 18. Resetting an user password after first user authentication (when an account has status FORCE_CHANGE_PASSWORD).
When an administrator creates the user pool account then an user has to change its password after first sign-in.
Moreover the Cognito User Pool service can send a list of required attributes that user has to set when settings a new password.
Use case 19. Using this library with Cognito's federated sign in on mobile devices.
Use flutter_webview (https://pub.dev/packages/webview_flutter) to navigate to Cognito's authorize URL.
Use flutter_webview's navigationDelegate to catch the redirect to myapp://?code=<AUTH_CODE>.
Make a POST request to Cognito's token URL to get your tokens.
Create the session and user with the tokens.
finalCompleter<WebViewController> _webViewController = Completer<WebViewController>();
WidgetgetWebView() {
var url = "https://${COGNITO_POOL_URL}" +
".amazoncognito.com/oauth2/authorize?identity_provider=Google&redirect_uri=" +
"myapp://&response_type=CODE&client_id=${COGNITO_CLIENT_ID}" +
"&scope=email%20openid%20profile%20aws.cognito.signin.user.admin";
returnWebView(
initialUrl: url,
userAgent: 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) ' +
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Mobile Safari/537.36',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_webViewController.complete(webViewController);
},
navigationDelegate: (NavigationRequest request) {
if (request.url.startsWith("myapp://?code=")) {
String code = request.url.substring("myapp://?code=".length);
signUserInWithAuthCode(code);
returnNavigationDecision.prevent;
}
returnNavigationDecision.navigate;
},
gestureNavigationEnabled: true,
);
}
final userPool = CognitoUserPool(
'ap-southeast-1_xxxxxxxxx',
'xxxxxxxxxxxxxxxxxxxxxxxxxx'
);
staticFuturesignUserInWithAuthCode(String authCode) async {
String url = "https://${COGNITO_POOL_URL}" +
".amazoncognito.com/oauth2/token?grant_type=authorization_code&client_id=" +
"${COGNITO_CLIENT_ID}&code=" + authCode + "&redirect_uri=myapp://";
final response = await http.post(url, body: {}, headers: {'Content-Type': 'application/x-www-form-urlencoded'});
if (response.statusCode != 200) {
throwException("Received bad status code from Cognito for auth code:" +
response.statusCode.toString() + "; body: " + response.body);
}
final tokenData = json.decode(response.body);
final idToken = CognitoIdToken(tokenData['id_token']);
final accessToken = CognitoAccessToken(tokenData['access_token']);
final refreshToken = CognitoRefreshToken(tokenData['refresh_token']);
final session = CognitoUserSession(idToken, accessToken, refreshToken: refreshToken);
final user = CognitoUser(null, userPool, signInUserSession: session);
// NOTE: in order to get the email from the list of user attributes, make sure you select email in the list of// attributes in Cognito and map it to the email field in the identity provider.final attributes = await user.getUserAttributes();
for (CognitoUserAttribute attribute in attributes) {
if (attribute.getName() == "email") {
user.username = attribute.getValue();
break;
}
}
return user;
}
}
Addtional Features
Get AWS Credentials
Get a authenticated user's AWS Credentials. Use with other signing processes like Signature Version 4.
import'package:amazon_cognito_identity_dart_2/cognito.dart';
final userPool = CognitoUserPool(
'ap-southeast-1_xxxxxxxxx',
'xxxxxxxxxxxxxxxxxxxxxxxxxx',
);
final cognitoUser = CognitoUser('email@inspire.my', userPool);
final authDetails = AuthenticationDetails(
username: 'email@inspire.my',
password: 'Password001',
);
final session = await cognitoUser.authenticateUser(authDetails);
final credentials = CognitoCredentials(
'ap-southeast-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', userPool);
await credentials.getAwsCredentials(session.getIdToken().getJwtToken());
print(credentials.accessKeyId);
print(credentials.secretAccessKey);
print(credentials.sessionToken);
Signing Requests
For S3 Uploads
S3 Upload to user "folder" using HTTP Presigned Post.
import'dart:convert';
import'dart:io';
import'package:path/path.dart'as path;
import'package:async/async.dart';
import'package:http/http.dart'as http;
import'package:amazon_cognito_identity_dart_2/cognito.dart';
import'package:amazon_cognito_identity_dart_2/sig_v4.dart';
classPolicy{
String expiration;
String region;
String bucket;
String key;
String credential;
String datetime;
String sessionToken;
int maxFileSize;
Policy(
this.key,
this.bucket,
this.datetime,
this.expiration,
this.credential,
this.maxFileSize,
this.sessionToken,
{
this.region = 'us-east-1',
});
factoryPolicy.fromS3PresignedPost(
String key,
String bucket,
int expiryMinutes,
String accessKeyId,
int maxFileSize,
String sessionToken, {
String region,
}) {
final datetime = SigV4.generateDatetime();
final expiration = (DateTime.now())
.add(Duration(minutes: expiryMinutes))
.toUtc()
.toString()
.split(' ')
.join('T');
final cred =
'$accessKeyId/${SigV4.buildCredentialScope(datetime, region, 's3')}';
final p = Policy(
key, bucket, datetime, expiration, cred, maxFileSize, sessionToken,
region: region);
return p;
}
Stringencode() {
final bytes = utf8.encode(toString());
return base64.encode(bytes);
}
@overrideStringtoString() {
// Safe to remove the "acl" line if your bucket has no ACL permissionsreturn'''
{ "expiration": "${this.expiration}",
"conditions": [
{"bucket": "${this.bucket}"},
["starts-with", "\$key", "${this.key}"],
{"acl": "public-read"},
["content-length-range", 1, ${this.maxFileSize}],
{"x-amz-credential": "${this.credential}"},
{"x-amz-algorithm": "AWS4-HMAC-SHA256"},
{"x-amz-date": "${this.datetime}" },
{"x-amz-security-token": "${this.sessionToken}" }
]
}
''';
}
}
voidmain() async {
const _awsUserPoolId = 'ap-southeast-xxxxxxxxxxx';
const _awsClientId = 'xxxxxxxxxxxxxxxxxxxxxxxxxx';
const _identityPoolId = 'ap-southeast-1:xxxxxxxxx-xxxx-xxxx-xxxxxxxxxxx';
final _userPool = CognitoUserPool(_awsUserPoolId, _awsClientId);
final _cognitoUser = CognitoUser('+60100000000', _userPool);
final authDetails =
AuthenticationDetails(username: '+60100000000', password: 'p&ssW0RD');
CognitoUserSession _session;
try {
_session = await _cognitoUser.authenticateUser(authDetails);
} catch (e) {
print(e);
}
final _credentials = CognitoCredentials(_identityPoolId, _userPool);
await _credentials.getAwsCredentials(_session.getIdToken().getJwtToken());
const _region = 'ap-southeast-1';
const _s3Endpoint = 'https://my-s3-bucket.s3-ap-southeast-1.amazonaws.com';
final file = File(path.join('/path/to/my/folder', 'square-cinnamon.jpg'));
final stream = http.ByteStream(DelegatingStream.typed(file.openRead()));
final length = await file.length();
final uri = Uri.parse(_s3Endpoint);
final req = http.MultipartRequest("POST", uri);
final multipartFile = http.MultipartFile('file', stream, length,
filename: path.basename(file.path));
finalString fileName = 'square-cinnamon.jpg';
finalString usrIdentityId = _credentials.userIdentityId;
finalString bucketKey = 'test/$usrIdentityId/$fileName';
final policy = Policy.fromS3PresignedPost(
bucketKey,
'my-s3-bucket',
15,
_credentials.accessKeyId,
length,
_credentials.sessionToken,
region: _region);
final key = SigV4.calculateSigningKey(
_credentials.secretAccessKey, policy.datetime, _region, 's3');
final signature = SigV4.calculateSignature(key, policy.encode());
req.files.add(multipartFile);
req.fields['key'] = policy.key;
req.fields['acl'] = 'public-read'; // Safe to remove this if your bucket has no ACL permissions
req.fields['X-Amz-Credential'] = policy.credential;
req.fields['X-Amz-Algorithm'] = 'AWS4-HMAC-SHA256';
req.fields['X-Amz-Date'] = policy.datetime;
req.fields['Policy'] = policy.encode();
req.fields['X-Amz-Signature'] = signature;
req.fields['x-amz-security-token'] = _credentials.sessionToken;
try {
final res = await req.send();
awaitfor (var value in res.stream.transform(utf8.decoder)) {
print(value);
}
} catch (e) {
print(e.toString());
}
}
The role attached to the identity pool (e.g. the ..._poolAuth role) should contain a policy like this:
By default you can refresh access and ID tokens with refreshSession(CognitoRefreshToken(...)), which uses Cognito InitiateAuth with REFRESH_TOKEN_AUTH.
If your app client has refresh token rotation enabled, use getTokensFromRefreshToken instead. That calls the GetTokensFromRefreshToken API so Cognito can return a new refresh token when it rotates; refreshSession does not surface rotated refresh tokens in that setup.
Reuse of an invalidated refresh token (for example after rotation) may yield CognitoClientException with code RefreshTokenReuseException — treat it as requiring a new sign-in.
The original amazon-cognito-identity-js which this library is based off, doesn't support client secret. This feature has been added since version 0.1.9, to use it, simply set your client secret when creating CognitoUserPool:
final userPool = CognitoUserPool(
'ap-southeast-1_xxxxxxxxx',
'xxxxxxxxxxxxxxxxxxxxxxxxxx',
clientSecret: 'xxxxxxxxxxxxxxxxxxxxxxx'
);
Flutter web sign-in performance
To improve performance of modPow() used in authenticateUser(), use an external JavaScript call to do the modPow calculation using the native BigInt type.
Include the following JavaScript method in your top level index.html page for your flutter web app.
Also, add package:js as a dependency in your app's pubspec.yaml
js: ^0.6.3
<script>
functionjsBigIntModPow(bStr, eStr, mStr) {
let b = BigInt(bStr);
let e = BigInt(eStr);
let m = BigInt(mStr);
let bigIntOne = BigInt(1);
let bigIntZero = BigInt(0);
if (e < bigIntOne) {
return bigIntOne;
}
if (b < bigIntZero || b > m) {
b = b % m;
}
var r = bigIntOne;
while (e > bigIntZero) {
if ((e & bigIntOne) > bigIntZero) {
r = (r * b) % m;
}
e = e >> bigIntOne;
b = (b * b) % m;
}
return r;
}
</script>